summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.eslintrc14
-rw-r--r--.gitignore1
-rw-r--r--.gitlab-ci.yml106
-rw-r--r--.gitlab/issue_templates/Bug.md24
-rw-r--r--.gitlab/issue_templates/Research Proposal.md17
-rw-r--r--.rubocop.yml299
-rw-r--r--.rubocop_todo.yml337
-rw-r--r--CHANGELOG.md225
-rw-r--r--CONTRIBUTING.md24
-rw-r--r--GITLAB_PAGES_VERSION2
-rw-r--r--Gemfile17
-rw-r--r--Gemfile.lock136
-rw-r--r--LICENSE2
-rw-r--r--PROCESS.md38
-rw-r--r--README.md2
-rw-r--r--VERSION2
-rwxr-xr-xapp/assets/images/favicon-blue.icobin0 -> 5430 bytes
-rw-r--r--app/assets/images/icon-merge-request-unmerged.svg1
-rw-r--r--app/assets/images/mailers/gitlab_footer_logo.gifbin0 -> 3654 bytes
-rw-r--r--app/assets/images/mailers/gitlab_header_logo.gifbin0 -> 3040 bytes
-rw-r--r--app/assets/javascripts/admin.js2
-rw-r--r--app/assets/javascripts/ajax_loading_spinner.js35
-rw-r--r--app/assets/javascripts/api.js4
-rw-r--r--app/assets/javascripts/application.js17
-rw-r--r--app/assets/javascripts/aside.js2
-rw-r--r--app/assets/javascripts/autosave.js2
-rw-r--r--app/assets/javascripts/awards_handler.js2
-rw-r--r--app/assets/javascripts/behaviors/autosize.js2
-rw-r--r--app/assets/javascripts/behaviors/details_behavior.js2
-rw-r--r--app/assets/javascripts/behaviors/quick_submit.js2
-rw-r--r--app/assets/javascripts/behaviors/requires_input.js2
-rw-r--r--app/assets/javascripts/blob/blob_file_dropzone.js2
-rw-r--r--app/assets/javascripts/blob/blob_gitignore_selector.js2
-rw-r--r--app/assets/javascripts/blob/blob_gitignore_selectors.js2
-rw-r--r--app/assets/javascripts/blob/blob_license_selector.js2
-rw-r--r--app/assets/javascripts/blob_edit/blob_edit_bundle.js2
-rw-r--r--app/assets/javascripts/blob_edit/edit_blob.js2
-rw-r--r--app/assets/javascripts/boards/boards_bundle.js.es665
-rw-r--r--app/assets/javascripts/boards/filters/due_date_filters.js.es63
-rw-r--r--app/assets/javascripts/breakpoints.js2
-rw-r--r--app/assets/javascripts/broadcast_message.js2
-rw-r--r--app/assets/javascripts/build.js20
-rw-r--r--app/assets/javascripts/build_artifacts.js2
-rw-r--r--app/assets/javascripts/commit.js2
-rw-r--r--app/assets/javascripts/commit/file.js2
-rw-r--r--app/assets/javascripts/commit/image_file.js2
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es69
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_service.js.es617
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_store.js.es610
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_table.js.es621
-rw-r--r--app/assets/javascripts/commits.js2
-rw-r--r--app/assets/javascripts/compare.js2
-rw-r--r--app/assets/javascripts/compare_autocomplete.js.es62
-rw-r--r--app/assets/javascripts/confirm_danger_modal.js2
-rw-r--r--app/assets/javascripts/copy_as_gfm.js.es63
-rw-r--r--app/assets/javascripts/copy_to_clipboard.js2
-rw-r--r--app/assets/javascripts/create_label.js.es66
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_plan_component.js.es66
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js.es624
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_store.js.es647
-rw-r--r--app/assets/javascripts/cycle_analytics/default_event_objects.js.es698
-rw-r--r--app/assets/javascripts/diff.js.es62
-rw-r--r--app/assets/javascripts/diff_notes/components/comment_resolve_btn.js.es611
-rw-r--r--app/assets/javascripts/diff_notes/components/jump_to_discussion.js.es613
-rw-r--r--app/assets/javascripts/diff_notes/components/resolve_btn.js.es619
-rw-r--r--app/assets/javascripts/diff_notes/components/resolve_count.js.es62
-rw-r--r--app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js.es613
-rw-r--r--app/assets/javascripts/diff_notes/diff_notes_bundle.js.es622
-rw-r--r--app/assets/javascripts/diff_notes/services/resolve.js.es648
-rw-r--r--app/assets/javascripts/dispatcher.js.es69
-rw-r--r--app/assets/javascripts/dropzone_input.js7
-rw-r--r--app/assets/javascripts/due_date_select.js.es661
-rw-r--r--app/assets/javascripts/environments/components/environment.js.es6364
-rw-r--r--app/assets/javascripts/environments/components/environment_actions.js.es687
-rw-r--r--app/assets/javascripts/environments/components/environment_external_url.js.es636
-rw-r--r--app/assets/javascripts/environments/components/environment_item.js.es61036
-rw-r--r--app/assets/javascripts/environments/components/environment_rollback.js.es655
-rw-r--r--app/assets/javascripts/environments/components/environment_stop.js.es645
-rw-r--r--app/assets/javascripts/environments/components/environment_terminal_button.js.es647
-rw-r--r--app/assets/javascripts/environments/components/environments_table.js.es674
-rw-r--r--app/assets/javascripts/environments/environments_bundle.js.es613
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_bundle.js.es613
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_view.js.es6182
-rw-r--r--app/assets/javascripts/environments/services/environments_service.js.es620
-rw-r--r--app/assets/javascripts/environments/stores/environments_store.js.es6280
-rw-r--r--app/assets/javascripts/extensions/jquery.js2
-rw-r--r--app/assets/javascripts/files_comment_button.js2
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_hint.js.es629
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_bundle.js12
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es629
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es611
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js.es615
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es65
-rw-r--r--app/assets/javascripts/flash.js2
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js.es69
-rw-r--r--app/assets/javascripts/gl_dropdown.js11
-rw-r--r--app/assets/javascripts/graphs/graphs_bundle.js7
-rw-r--r--app/assets/javascripts/graphs/stat_graph.js2
-rw-r--r--app/assets/javascripts/graphs/stat_graph_contributors.js2
-rw-r--r--app/assets/javascripts/graphs/stat_graph_contributors_graph.js2
-rw-r--r--app/assets/javascripts/graphs/stat_graph_contributors_util.js2
-rw-r--r--app/assets/javascripts/group_avatar.js2
-rw-r--r--app/assets/javascripts/groups_select.js2
-rw-r--r--app/assets/javascripts/header.js2
-rw-r--r--app/assets/javascripts/importer_status.js2
-rw-r--r--app/assets/javascripts/issuable_context.js2
-rw-r--r--app/assets/javascripts/issuable_form.js19
-rw-r--r--app/assets/javascripts/issue.js48
-rw-r--r--app/assets/javascripts/issue_status_select.js2
-rw-r--r--app/assets/javascripts/label_manager.js.es626
-rw-r--r--app/assets/javascripts/labels.js2
-rw-r--r--app/assets/javascripts/labels_select.js2
-rw-r--r--app/assets/javascripts/layout_nav.js2
-rw-r--r--app/assets/javascripts/lib/cropper.js2
-rw-r--r--app/assets/javascripts/lib/raphael.js2
-rw-r--r--app/assets/javascripts/lib/utils/animate.js2
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js.es674
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js.es64
-rw-r--r--app/assets/javascripts/lib/utils/notify.js2
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js15
-rw-r--r--app/assets/javascripts/lib/utils/type_utility.js2
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js.es62
-rw-r--r--app/assets/javascripts/line_highlighter.js2
-rw-r--r--app/assets/javascripts/logo.js2
-rw-r--r--app/assets/javascripts/member_expiration_date.js.es636
-rw-r--r--app/assets/javascripts/merge_request.js53
-rw-r--r--app/assets/javascripts/merge_request_tabs.js.es628
-rw-r--r--app/assets/javascripts/merge_request_widget.js.es65
-rw-r--r--app/assets/javascripts/merged_buttons.js2
-rw-r--r--app/assets/javascripts/milestone.js162
-rw-r--r--app/assets/javascripts/milestone_select.js7
-rw-r--r--app/assets/javascripts/mini_pipeline_graph_dropdown.js.es62
-rw-r--r--app/assets/javascripts/namespace_select.js6
-rw-r--r--app/assets/javascripts/network/branch_graph.js2
-rw-r--r--app/assets/javascripts/network/network.js2
-rw-r--r--app/assets/javascripts/network/network_bundle.js7
-rw-r--r--app/assets/javascripts/new_branch_form.js2
-rw-r--r--app/assets/javascripts/new_commit_form.js2
-rw-r--r--app/assets/javascripts/notes.js39
-rw-r--r--app/assets/javascripts/notifications_dropdown.js2
-rw-r--r--app/assets/javascripts/notifications_form.js2
-rw-r--r--app/assets/javascripts/profile/profile.js.es61
-rw-r--r--app/assets/javascripts/project.js2
-rw-r--r--app/assets/javascripts/project_avatar.js2
-rw-r--r--app/assets/javascripts/project_find_file.js2
-rw-r--r--app/assets/javascripts/project_fork.js2
-rw-r--r--app/assets/javascripts/project_import.js2
-rw-r--r--app/assets/javascripts/project_label_subscription.js.es66
-rw-r--r--app/assets/javascripts/project_new.js2
-rw-r--r--app/assets/javascripts/project_select.js2
-rw-r--r--app/assets/javascripts/project_show.js2
-rw-r--r--app/assets/javascripts/projects_list.js2
-rw-r--r--app/assets/javascripts/render_gfm.js2
-rw-r--r--app/assets/javascripts/render_math.js2
-rw-r--r--app/assets/javascripts/right_sidebar.js20
-rw-r--r--app/assets/javascripts/search.js2
-rw-r--r--app/assets/javascripts/search_autocomplete.js.es64
-rw-r--r--app/assets/javascripts/shortcuts.js2
-rw-r--r--app/assets/javascripts/shortcuts_dashboard_navigation.js2
-rw-r--r--app/assets/javascripts/shortcuts_find_file.js2
-rw-r--r--app/assets/javascripts/shortcuts_issuable.js2
-rw-r--r--app/assets/javascripts/shortcuts_navigation.js2
-rw-r--r--app/assets/javascripts/shortcuts_network.js2
-rw-r--r--app/assets/javascripts/sidebar.js.es698
-rw-r--r--app/assets/javascripts/single_file_diff.js6
-rw-r--r--app/assets/javascripts/snippet/snippet_bundle.js2
-rw-r--r--app/assets/javascripts/star.js2
-rw-r--r--app/assets/javascripts/subscription_select.js2
-rw-r--r--app/assets/javascripts/syntax_highlight.js2
-rw-r--r--app/assets/javascripts/task_list.js40
-rw-r--r--app/assets/javascripts/test_utils/simulate_drag.js (renamed from app/assets/javascripts/boards/test_utils/simulate_drag.js)5
-rw-r--r--app/assets/javascripts/todos.js.es6156
-rw-r--r--app/assets/javascripts/tree.js2
-rw-r--r--app/assets/javascripts/u2f/error.js2
-rw-r--r--app/assets/javascripts/u2f/register.js2
-rw-r--r--app/assets/javascripts/u2f/util.js2
-rw-r--r--app/assets/javascripts/users/calendar.js2
-rw-r--r--app/assets/javascripts/users_select.js2
-rw-r--r--app/assets/javascripts/version_check_image.js.es616
-rw-r--r--app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es61
-rw-r--r--app/assets/javascripts/vue_pipelines_index/pipelines.js.es626
-rw-r--r--app/assets/javascripts/vue_pipelines_index/stage.js.es636
-rw-r--r--app/assets/javascripts/vue_pipelines_index/store.js.es665
-rw-r--r--app/assets/javascripts/vue_realtime_listener/index.js.es611
-rw-r--r--app/assets/javascripts/vue_shared/components/commit.js.es61
-rw-r--r--app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es66
-rw-r--r--app/assets/javascripts/vue_shared/components/table_pagination.js.es64
-rw-r--r--app/assets/javascripts/wikis.js.es66
-rw-r--r--app/assets/javascripts/zen_mode.js2
-rw-r--r--app/assets/stylesheets/application.scss3
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/animations.scss6
-rw-r--r--app/assets/stylesheets/framework/avatar.scss2
-rw-r--r--app/assets/stylesheets/framework/calendar.scss55
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss124
-rw-r--r--app/assets/stylesheets/framework/files.scss43
-rw-r--r--app/assets/stylesheets/framework/gitlab-theme.scss144
-rw-r--r--app/assets/stylesheets/framework/header.scss45
-rw-r--r--app/assets/stylesheets/framework/jquery.scss57
-rw-r--r--app/assets/stylesheets/framework/lists.scss10
-rw-r--r--app/assets/stylesheets/framework/mixins.scss7
-rw-r--r--app/assets/stylesheets/framework/nav.scss14
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss198
-rw-r--r--app/assets/stylesheets/framework/typography.scss25
-rw-r--r--app/assets/stylesheets/framework/variables.scss4
-rw-r--r--app/assets/stylesheets/mailers/highlighted_diff_email.scss7
-rw-r--r--app/assets/stylesheets/pages/boards.scss6
-rw-r--r--app/assets/stylesheets/pages/builds.scss2
-rw-r--r--app/assets/stylesheets/pages/cycle_analytics.scss6
-rw-r--r--app/assets/stylesheets/pages/diff.scss7
-rw-r--r--app/assets/stylesheets/pages/environments.scss40
-rw-r--r--app/assets/stylesheets/pages/events.scss1
-rw-r--r--app/assets/stylesheets/pages/issuable.scss20
-rw-r--r--app/assets/stylesheets/pages/issues.scss7
-rw-r--r--app/assets/stylesheets/pages/labels.scss16
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss32
-rw-r--r--app/assets/stylesheets/pages/milestone.scss26
-rw-r--r--app/assets/stylesheets/pages/notes.scss11
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss21
-rw-r--r--app/assets/stylesheets/pages/profile.scss4
-rw-r--r--app/assets/stylesheets/pages/profiles/preferences.scss39
-rw-r--r--app/assets/stylesheets/pages/projects.scss16
-rw-r--r--app/assets/stylesheets/pages/todos.scss58
-rw-r--r--app/assets/stylesheets/pages/tree.scss2
-rw-r--r--app/assets/stylesheets/pages/wiki.scss30
-rw-r--r--app/assets/stylesheets/print.scss1
-rw-r--r--app/controllers/admin/application_settings_controller.rb1
-rw-r--r--app/controllers/admin/background_jobs_controller.rb1
-rw-r--r--app/controllers/admin/dashboard_controller.rb4
-rw-r--r--app/controllers/admin/groups_controller.rb4
-rw-r--r--app/controllers/admin/runners_controller.rb6
-rw-r--r--app/controllers/admin/system_info_controller.rb5
-rw-r--r--app/controllers/admin/users_controller.rb1
-rw-r--r--app/controllers/application_controller.rb27
-rw-r--r--app/controllers/concerns/creates_commit.rb27
-rw-r--r--app/controllers/concerns/issuable_collections.rb30
-rw-r--r--app/controllers/concerns/issues_action.rb3
-rw-r--r--app/controllers/concerns/merge_requests_action.rb3
-rw-r--r--app/controllers/concerns/service_params.rb4
-rw-r--r--app/controllers/concerns/snippets_actions.rb21
-rw-r--r--app/controllers/concerns/spammable_actions.rb30
-rw-r--r--app/controllers/dashboard/groups_controller.rb2
-rw-r--r--app/controllers/dashboard/projects_controller.rb23
-rw-r--r--app/controllers/dashboard/todos_controller.rb17
-rw-r--r--app/controllers/explore/application_controller.rb2
-rw-r--r--app/controllers/groups/group_members_controller.rb2
-rw-r--r--app/controllers/groups_controller.rb35
-rw-r--r--app/controllers/help_controller.rb2
-rw-r--r--app/controllers/import/fogbugz_controller.rb2
-rw-r--r--app/controllers/import/google_code_controller.rb4
-rw-r--r--app/controllers/invites_controller.rb4
-rw-r--r--app/controllers/jwt_controller.rb8
-rw-r--r--app/controllers/koding_controller.rb2
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb9
-rw-r--r--app/controllers/profiles/keys_controller.rb4
-rw-r--r--app/controllers/profiles/notifications_controller.rb2
-rw-r--r--app/controllers/profiles/preferences_controller.rb1
-rw-r--r--app/controllers/profiles/two_factor_auths_controller.rb2
-rw-r--r--app/controllers/projects/application_controller.rb1
-rw-r--r--app/controllers/projects/blob_controller.rb10
-rw-r--r--app/controllers/projects/branches_controller.rb30
-rw-r--r--app/controllers/projects/environments_controller.rb35
-rw-r--r--app/controllers/projects/git_http_client_controller.rb11
-rw-r--r--app/controllers/projects/issues_controller.rb41
-rw-r--r--app/controllers/projects/lfs_api_controller.rb4
-rw-r--r--app/controllers/projects/merge_requests_controller.rb35
-rw-r--r--app/controllers/projects/notes_controller.rb2
-rw-r--r--app/controllers/projects/pipelines_controller.rb12
-rw-r--r--app/controllers/projects/protected_branches_controller.rb8
-rw-r--r--app/controllers/projects/runners_controller.rb6
-rw-r--r--app/controllers/projects/snippets_controller.rb34
-rw-r--r--app/controllers/projects/tags_controller.rb4
-rw-r--r--app/controllers/projects/tree_controller.rb4
-rw-r--r--app/controllers/projects/uploads_controller.rb4
-rw-r--r--app/controllers/projects/wikis_controller.rb5
-rw-r--r--app/controllers/registrations_controller.rb2
-rw-r--r--app/controllers/search_controller.rb2
-rw-r--r--app/controllers/sessions_controller.rb11
-rw-r--r--app/controllers/snippets_controller.rb35
-rw-r--r--app/finders/group_members_finder.rb20
-rw-r--r--app/finders/groups_finder.rb2
-rw-r--r--app/finders/issuable_finder.rb17
-rw-r--r--app/finders/issues_finder.rb4
-rw-r--r--app/finders/merge_requests_finder.rb10
-rw-r--r--app/finders/notes_finder.rb11
-rw-r--r--app/finders/pipelines_finder.rb6
-rw-r--r--app/finders/projects_finder.rb2
-rw-r--r--app/finders/todos_finder.rb4
-rw-r--r--app/helpers/application_helper.rb20
-rw-r--r--app/helpers/application_settings_helper.rb25
-rw-r--r--app/helpers/blob_helper.rb21
-rw-r--r--app/helpers/builds_helper.rb2
-rw-r--r--app/helpers/emails_helper.rb19
-rw-r--r--app/helpers/issuables_helper.rb6
-rw-r--r--app/helpers/javascript_helper.rb1
-rw-r--r--app/helpers/merge_requests_helper.rb12
-rw-r--r--app/helpers/namespaces_helper.rb8
-rw-r--r--app/helpers/nav_helper.rb14
-rw-r--r--app/helpers/page_layout_helper.rb4
-rw-r--r--app/helpers/preferences_helper.rb6
-rw-r--r--app/helpers/projects_helper.rb9
-rw-r--r--app/helpers/sorting_helper.rb2
-rw-r--r--app/helpers/submodule_helper.rb10
-rw-r--r--app/helpers/tab_helper.rb2
-rw-r--r--app/helpers/todos_helper.rb12
-rw-r--r--app/helpers/visibility_level_helper.rb10
-rw-r--r--app/helpers/wiki_helper.rb13
-rw-r--r--app/mailers/emails/pipelines.rb4
-rw-r--r--app/mailers/notify.rb2
-rw-r--r--app/mailers/repository_check_mailer.rb11
-rw-r--r--app/models/application_setting.rb49
-rw-r--r--app/models/award_emoji.rb8
-rw-r--r--app/models/ci/build.rb47
-rw-r--r--app/models/ci/pipeline.rb33
-rw-r--r--app/models/ci/runner.rb20
-rw-r--r--app/models/ci/runner_project.rb2
-rw-r--r--app/models/ci/trigger.rb4
-rw-r--r--app/models/commit.rb17
-rw-r--r--app/models/commit_status.rb11
-rw-r--r--app/models/concerns/cache_markdown_field.rb11
-rw-r--r--app/models/concerns/case_sensitivity.rb11
-rw-r--r--app/models/concerns/has_status.rb12
-rw-r--r--app/models/concerns/issuable.rb37
-rw-r--r--app/models/concerns/mentionable.rb19
-rw-r--r--app/models/concerns/milestoneish.rb2
-rw-r--r--app/models/concerns/reactive_service.rb2
-rw-r--r--app/models/concerns/routable.rb66
-rw-r--r--app/models/concerns/sortable.rb11
-rw-r--r--app/models/concerns/spammable.rb6
-rw-r--r--app/models/concerns/uniquify.rb30
-rw-r--r--app/models/diff_note.rb2
-rw-r--r--app/models/directly_addressed_user.rb7
-rw-r--r--app/models/environment.rb2
-rw-r--r--app/models/event.rb11
-rw-r--r--app/models/external_issue.rb2
-rw-r--r--app/models/group.rb4
-rw-r--r--app/models/group_milestone.rb2
-rw-r--r--app/models/issue.rb2
-rw-r--r--app/models/label.rb2
-rw-r--r--app/models/member.rb5
-rw-r--r--app/models/members/group_member.rb4
-rw-r--r--app/models/members/project_member.rb4
-rw-r--r--app/models/merge_request.rb26
-rw-r--r--app/models/merge_request_diff.rb2
-rw-r--r--app/models/merge_requests_closing_issues.rb8
-rw-r--r--app/models/namespace.rb62
-rw-r--r--app/models/network/graph.rb11
-rw-r--r--app/models/note.rb8
-rw-r--r--app/models/notification_setting.rb4
-rw-r--r--app/models/pages_domain.rb2
-rw-r--r--app/models/project.rb119
-rw-r--r--app/models/project_feature.rb2
-rw-r--r--app/models/project_services/buildkite_service.rb2
-rw-r--r--app/models/project_services/chat_message/base_message.rb4
-rw-r--r--app/models/project_services/chat_message/build_message.rb28
-rw-r--r--app/models/project_services/chat_message/issue_message.rb7
-rw-r--r--app/models/project_services/chat_message/merge_message.rb4
-rw-r--r--app/models/project_services/chat_message/note_message.rb9
-rw-r--r--app/models/project_services/drone_ci_service.rb14
-rw-r--r--app/models/project_services/gitlab_ci_service.rb8
-rw-r--r--app/models/project_services/hipchat_service.rb4
-rw-r--r--app/models/project_services/irker_service.rb5
-rw-r--r--app/models/project_services/jira_service.rb10
-rw-r--r--app/models/project_services/kubernetes_service.rb16
-rw-r--r--app/models/project_services/pivotaltracker_service.rb2
-rw-r--r--app/models/project_services/pushover_service.rb45
-rw-r--r--app/models/project_snippet.rb4
-rw-r--r--app/models/project_statistics.rb2
-rw-r--r--app/models/project_wiki.rb9
-rw-r--r--app/models/protected_branch.rb4
-rw-r--r--app/models/repository.rb22
-rw-r--r--app/models/route.rb27
-rw-r--r--app/models/service.rb2
-rw-r--r--app/models/todo.rb18
-rw-r--r--app/models/user.rb84
-rw-r--r--app/models/wiki_directory.rb18
-rw-r--r--app/models/wiki_page.rb51
-rw-r--r--app/policies/base_policy.rb4
-rw-r--r--app/policies/user_policy.rb8
-rw-r--r--app/serializers/analytics_stage_entity.rb1
-rw-r--r--app/serializers/environment_serializer.rb52
-rw-r--r--app/serializers/pipeline_serializer.rb40
-rw-r--r--app/services/auth/container_registry_authentication_service.rb2
-rw-r--r--app/services/base_service.rb9
-rw-r--r--app/services/ci/create_pipeline_builds_service.rb4
-rw-r--r--app/services/ci/create_pipeline_service.rb3
-rw-r--r--app/services/ci/retry_build_service.rb43
-rw-r--r--app/services/ci/retry_pipeline_service.rb28
-rw-r--r--app/services/ci/update_runner_service.rb15
-rw-r--r--app/services/create_branch_service.rb14
-rw-r--r--app/services/create_snippet_service.rb10
-rw-r--r--app/services/create_tag_service.rb30
-rw-r--r--app/services/delete_tag_service.rb42
-rw-r--r--app/services/delete_user_service.rb31
-rw-r--r--app/services/destroy_group_service.rb29
-rw-r--r--app/services/files/destroy_service.rb (renamed from app/services/files/delete_service.rb)2
-rw-r--r--app/services/files/multi_service.rb8
-rw-r--r--app/services/groups/destroy_service.rb27
-rw-r--r--app/services/issuable_base_service.rb32
-rw-r--r--app/services/issues/build_service.rb10
-rw-r--r--app/services/issues/create_service.rb21
-rw-r--r--app/services/issues/update_service.rb8
-rw-r--r--app/services/members/destroy_service.rb2
-rw-r--r--app/services/merge_requests/add_todo_when_build_fails_service.rb6
-rw-r--r--app/services/merge_requests/build_service.rb26
-rw-r--r--app/services/merge_requests/merge_service.rb18
-rw-r--r--app/services/merge_requests/refresh_service.rb6
-rw-r--r--app/services/notes/destroy_service.rb (renamed from app/services/notes/delete_service.rb)2
-rw-r--r--app/services/notes/slash_commands_service.rb2
-rw-r--r--app/services/notification_service.rb11
-rw-r--r--app/services/projects/create_service.rb2
-rw-r--r--app/services/projects/destroy_service.rb5
-rw-r--r--app/services/projects/download_service.rb4
-rw-r--r--app/services/projects/participants_service.rb2
-rw-r--r--app/services/projects/transfer_service.rb6
-rw-r--r--app/services/projects/update_pages_service.rb2
-rw-r--r--app/services/projects/upload_service.rb2
-rw-r--r--app/services/protected_branches/api_update_service.rb8
-rw-r--r--app/services/spam_check_service.rb24
-rw-r--r--app/services/spam_service.rb31
-rw-r--r--app/services/system_hooks_service.rb4
-rw-r--r--app/services/system_note_service.rb17
-rw-r--r--app/services/tags/create_service.rb32
-rw-r--r--app/services/tags/destroy_service.rb44
-rw-r--r--app/services/todo_service.rb43
-rw-r--r--app/services/update_snippet_service.rb10
-rw-r--r--app/services/users/destroy_service.rb56
-rw-r--r--app/services/wiki_pages/destroy_service.rb11
-rw-r--r--app/uploaders/file_uploader.rb2
-rw-r--r--app/uploaders/uploader_helper.rb11
-rw-r--r--app/validators/addressable_url_validator.rb2
-rw-r--r--app/validators/duration_validator.rb17
-rw-r--r--app/views/admin/abuse_reports/index.html.haml1
-rw-r--r--app/views/admin/application_settings/_form.html.haml16
-rw-r--r--app/views/admin/background_jobs/show.html.haml2
-rw-r--r--app/views/admin/dashboard/index.html.haml2
-rw-r--r--app/views/admin/logs/show.html.haml7
-rw-r--r--app/views/admin/projects/index.html.haml8
-rw-r--r--app/views/admin/runners/_runner.html.haml2
-rw-r--r--app/views/admin/runners/index.html.haml2
-rw-r--r--app/views/admin/users/_user.html.haml2
-rw-r--r--app/views/admin/users/index.html.haml14
-rw-r--r--app/views/admin/users/show.html.haml5
-rw-r--r--app/views/ci/lints/show.html.haml2
-rw-r--r--app/views/dashboard/_activity_head.html.haml15
-rw-r--r--app/views/dashboard/todos/_todo.html.haml22
-rw-r--r--app/views/dashboard/todos/index.html.haml6
-rw-r--r--app/views/devise/shared/_signin_box.html.haml2
-rw-r--r--app/views/devise/shared/_signup_box.html.haml2
-rw-r--r--app/views/devise/shared/_tabs_ldap.html.haml2
-rw-r--r--app/views/discussions/_diff_with_notes.html.haml2
-rw-r--r--app/views/discussions/_resolve_all.html.haml3
-rw-r--r--app/views/events/event/_push.html.haml8
-rw-r--r--app/views/explore/projects/_filter.html.haml4
-rw-r--r--app/views/groups/_head.html.haml19
-rw-r--r--app/views/groups/_head_issues.html.haml19
-rw-r--r--app/views/groups/_home_panel.html.haml17
-rw-r--r--app/views/groups/_show_nav.html.haml7
-rw-r--r--app/views/groups/activity.html.haml3
-rw-r--r--app/views/groups/group_members/index.html.haml1
-rw-r--r--app/views/groups/issues.html.haml1
-rw-r--r--app/views/groups/labels/index.html.haml1
-rw-r--r--app/views/groups/milestones/index.html.haml1
-rw-r--r--app/views/groups/milestones/show.html.haml4
-rw-r--r--app/views/groups/show.html.haml44
-rw-r--r--app/views/groups/subgroups.html.haml20
-rw-r--r--app/views/help/ui.html.haml2
-rw-r--r--app/views/import/base/unauthorized.js.haml2
-rw-r--r--app/views/kaminari/gitlab/_page.html.haml2
-rw-r--r--app/views/layouts/_head.html.haml2
-rw-r--r--app/views/layouts/_page.html.haml19
-rw-r--r--app/views/layouts/_recaptcha_verification.html.haml23
-rw-r--r--app/views/layouts/application.html.haml2
-rw-r--r--app/views/layouts/header/_default.html.haml17
-rw-r--r--app/views/layouts/mailer.html.haml72
-rw-r--r--app/views/layouts/mailer.text.haml5
-rw-r--r--app/views/layouts/nav/_admin.html.haml2
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml76
-rw-r--r--app/views/layouts/nav/_explore.html.haml2
-rw-r--r--app/views/layouts/nav/_group.html.haml20
-rw-r--r--app/views/notify/pipeline_failed_email.html.haml280
-rw-r--r--app/views/notify/pipeline_failed_email.text.erb4
-rw-r--r--app/views/notify/pipeline_success_email.html.haml230
-rw-r--r--app/views/notify/pipeline_success_email.text.erb4
-rw-r--r--app/views/profiles/accounts/show.html.haml5
-rw-r--r--app/views/profiles/notifications/show.html.haml5
-rw-r--r--app/views/profiles/personal_access_tokens/index.html.haml16
-rw-r--r--app/views/profiles/preferences/show.html.haml13
-rw-r--r--app/views/profiles/preferences/update.js.erb4
-rw-r--r--app/views/projects/_home_panel.html.haml10
-rw-r--r--app/views/projects/_last_push.html.haml1
-rw-r--r--app/views/projects/blame/show.html.haml2
-rw-r--r--app/views/projects/blob/_blob.html.haml2
-rw-r--r--app/views/projects/blob/_editor.html.haml2
-rw-r--r--app/views/projects/blob/diff.html.haml2
-rw-r--r--app/views/projects/boards/_show.html.haml2
-rw-r--r--app/views/projects/branches/_branch.html.haml2
-rw-r--r--app/views/projects/builds/_sidebar.html.haml2
-rw-r--r--app/views/projects/buttons/_download.html.haml10
-rw-r--r--app/views/projects/ci/pipelines/_pipeline.html.haml6
-rw-r--r--app/views/projects/commit/_pipeline.html.haml2
-rw-r--r--app/views/projects/commit/_pipelines_list.haml5
-rw-r--r--app/views/projects/commits/show.html.haml4
-rw-r--r--app/views/projects/compare/_form.html.haml4
-rw-r--r--app/views/projects/diffs/_content.html.haml7
-rw-r--r--app/views/projects/diffs/_file.html.haml5
-rw-r--r--app/views/projects/diffs/_file_header.html.haml6
-rw-r--r--app/views/projects/diffs/_parallel_view.html.haml7
-rw-r--r--app/views/projects/diffs/_text_file.html.haml7
-rw-r--r--app/views/projects/edit.html.haml8
-rw-r--r--app/views/projects/environments/folder.html.haml13
-rw-r--r--app/views/projects/environments/terminal.html.haml2
-rw-r--r--app/views/projects/group_links/_index.html.haml2
-rw-r--r--app/views/projects/issues/_discussion.html.haml4
-rw-r--r--app/views/projects/issues/_issue.html.haml96
-rw-r--r--app/views/projects/issues/show.html.haml12
-rw-r--r--app/views/projects/issues/verify.html.haml22
-rw-r--r--app/views/projects/labels/index.html.haml3
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml126
-rw-r--r--app/views/projects/merge_requests/_show.html.haml5
-rw-r--r--app/views/projects/merge_requests/conflicts.html.haml2
-rw-r--r--app/views/projects/merge_requests/index.html.haml7
-rw-r--r--app/views/projects/merge_requests/widget/_heading.html.haml7
-rw-r--r--app/views/projects/merge_requests/widget/_show.html.haml8
-rw-r--r--app/views/projects/merge_requests/widget/open/_build_failed.html.haml2
-rw-r--r--app/views/projects/milestones/show.html.haml3
-rw-r--r--app/views/projects/new.html.haml9
-rw-r--r--app/views/projects/notes/_hints.html.haml1
-rw-r--r--app/views/projects/notes/_note.html.haml6
-rw-r--r--app/views/projects/pages_domains/show.html.haml2
-rw-r--r--app/views/projects/pipelines/_info.html.haml6
-rw-r--r--app/views/projects/pipelines/index.html.haml22
-rw-r--r--app/views/projects/pipelines_settings/_badge.html.haml7
-rw-r--r--app/views/projects/pipelines_settings/_show.html.haml2
-rw-r--r--app/views/projects/runners/_form.html.haml2
-rw-r--r--app/views/projects/runners/_runner.html.haml2
-rw-r--r--app/views/projects/runners/show.html.haml2
-rw-r--r--app/views/projects/snippets/_actions.html.haml4
-rw-r--r--app/views/projects/snippets/show.html.haml2
-rw-r--r--app/views/projects/snippets/verify.html.haml4
-rw-r--r--app/views/projects/tree/_readme.html.haml2
-rw-r--r--app/views/projects/triggers/_index.html.haml4
-rw-r--r--app/views/projects/variables/_form.html.haml2
-rw-r--r--app/views/projects/wikis/_new.html.haml4
-rw-r--r--app/views/projects/wikis/_pages_wiki_page.html.haml5
-rw-r--r--app/views/projects/wikis/_sidebar.html.haml8
-rw-r--r--app/views/projects/wikis/_sidebar_wiki_page.html.haml3
-rw-r--r--app/views/projects/wikis/_wiki_directory.html.haml4
-rw-r--r--app/views/projects/wikis/_wiki_page.html.haml1
-rw-r--r--app/views/projects/wikis/pages.html.haml10
-rw-r--r--app/views/projects/wikis/show.html.haml4
-rw-r--r--app/views/search/_category.html.haml26
-rw-r--r--app/views/search/_results.html.haml2
-rw-r--r--app/views/search/results/_blob.html.haml2
-rw-r--r--app/views/search/results/_snippet_blob.html.haml57
-rw-r--r--app/views/search/results/_snippet_title.html.haml2
-rw-r--r--app/views/search/results/_wiki_blob.html.haml2
-rw-r--r--app/views/shared/_commit_message_container.html.haml4
-rw-r--r--app/views/shared/_group_form.html.haml9
-rw-r--r--app/views/shared/_issuable_meta_data.html.haml25
-rw-r--r--app/views/shared/_label.html.haml10
-rw-r--r--app/views/shared/_visibility_level.html.haml11
-rw-r--r--app/views/shared/builds/_tabs.html.haml8
-rw-r--r--app/views/shared/groups/_group.html.haml6
-rw-r--r--app/views/shared/issuable/_nav.html.haml10
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml10
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml4
-rw-r--r--app/views/shared/members/_group.html.haml2
-rw-r--r--app/views/shared/members/_member.html.haml9
-rw-r--r--app/views/shared/milestones/_form_dates.html.haml3
-rw-r--r--app/views/shared/milestones/_issuable.html.haml2
-rw-r--r--app/views/shared/milestones/_issuables.html.haml6
-rw-r--r--app/views/shared/projects/_dropdown.html.haml12
-rw-r--r--app/views/shared/snippets/_form.html.haml2
-rw-r--r--app/views/sherlock/file_samples/show.html.haml2
-rw-r--r--app/views/snippets/_actions.html.haml4
-rw-r--r--app/views/snippets/_snippets_scope_menu.html.haml8
-rw-r--r--app/views/snippets/show.html.haml2
-rw-r--r--app/views/snippets/verify.html.haml4
-rw-r--r--app/views/users/calendar.html.haml2
-rw-r--r--app/views/users/calendar_activities.html.haml12
-rw-r--r--app/views/users/show.html.haml2
-rw-r--r--app/workers/authorized_projects_worker.rb2
-rw-r--r--app/workers/delete_user_worker.rb4
-rw-r--r--app/workers/group_destroy_worker.rb2
-rw-r--r--app/workers/irker_worker.rb6
-rw-r--r--changelogs/unreleased/1051-api-create-users-without-password.yml4
-rw-r--r--changelogs/unreleased/12726-preserve-issues-after-deleting-users.yml4
-rw-r--r--changelogs/unreleased/1363-redo-mailroom-support.yml4
-rw-r--r--changelogs/unreleased/14492-change-fork-endpoint.yml4
-rw-r--r--changelogs/unreleased/17662-rename-builds.yml4
-rw-r--r--changelogs/unreleased/19302-wiki-page-delete-does-not-trigger-the-webhook.yml4
-rw-r--r--changelogs/unreleased/20452-remove-require-from-request_profiler-initializer.yml4
-rw-r--r--changelogs/unreleased/20495-plus-icon-button.yml4
-rw-r--r--changelogs/unreleased/20732_member_exists_409.yml4
-rw-r--r--changelogs/unreleased/20852-getting-started-project-better-blank-state-for-labels-view.yml4
-rw-r--r--changelogs/unreleased/21240_snippets_line_ending.yml4
-rw-r--r--changelogs/unreleased/21518_recaptcha_spam_issues.yml4
-rw-r--r--changelogs/unreleased/22007-unify-projects-search.yml4
-rw-r--r--changelogs/unreleased/22018-api-milestone-merge-requests.yml4
-rw-r--r--changelogs/unreleased/22132-rename-branch-name-params-to-branch.yml4
-rw-r--r--changelogs/unreleased/22466-task-list-alignment.yml4
-rw-r--r--changelogs/unreleased/22638-creating-a-branch-matching-a-wildcard-fails.yml4
-rw-r--r--changelogs/unreleased/22645-add-discussion-contribs-to-calendar.yml4
-rw-r--r--changelogs/unreleased/22818-licence-gitignore-and-yml-endpoints-removal.yml4
-rw-r--r--changelogs/unreleased/22974-trigger-service-events-through-api.yml4
-rw-r--r--changelogs/unreleased/23061-consolidate-project-lists.yml4
-rw-r--r--changelogs/unreleased/23104-remove-public-param-for-projects.yml4
-rw-r--r--changelogs/unreleased/23524-notify-automerge-user-of-failed-build.yml4
-rw-r--r--changelogs/unreleased/23535-folders-in-wiki-repository.yml4
-rw-r--r--changelogs/unreleased/23634-remove-project-grouping.yml4
-rw-r--r--changelogs/unreleased/23767-disable-storing-of-sensitive-information.yml4
-rw-r--r--changelogs/unreleased/23819-fix-milestone-counters-to-top-right-of-panel-headings.yml4
-rw-r--r--changelogs/unreleased/24147-delete-env-button.yml4
-rw-r--r--changelogs/unreleased/24333-close-issues-with-merge-request-title-ui.yml5
-rw-r--r--changelogs/unreleased/24462-reduce_ldap_queries_for_lfs.yml4
-rw-r--r--changelogs/unreleased/24606-force-password-reset-on-next-login.yml4
-rw-r--r--changelogs/unreleased/24716-fix-ctrl-click-links.yml4
-rw-r--r--changelogs/unreleased/24795_refactor_merge_request_build_service.yml4
-rw-r--r--changelogs/unreleased/24833-Allow-to-search-by-commit-hash-within-project.yml4
-rw-r--r--changelogs/unreleased/24923_nested_tasks.yml4
-rw-r--r--changelogs/unreleased/24976-start-of-line-mention.yml4
-rw-r--r--changelogs/unreleased/25134-mobile-issue-view-doesn-t-show-organization-membership.yml4
-rw-r--r--changelogs/unreleased/25312-search-input-cmd-click-issue.yml4
-rw-r--r--changelogs/unreleased/25360-remove-flash-warning-from-login-page.yml4
-rw-r--r--changelogs/unreleased/25460-replace-word-users-with-members.yml4
-rw-r--r--changelogs/unreleased/25465-todo-done-clicking-is-kind-of-unsafe.yml4
-rw-r--r--changelogs/unreleased/25624-anticipate-obstacles-to-removing-turbolinks.yml4
-rw-r--r--changelogs/unreleased/25709-diff-file-overflow.yml4
-rw-r--r--changelogs/unreleased/25811-pipeline-number-and-url-do-not-update-when-new-pipeline-is-triggered.yml4
-rw-r--r--changelogs/unreleased/25910-convert-manual-action-icons-to-svg-to-propperly-position-them.yml4
-rw-r--r--changelogs/unreleased/25989-fix-rogue-scrollbars-on-comments.yml4
-rw-r--r--changelogs/unreleased/26059-segoe-ui-vertical.yml4
-rw-r--r--changelogs/unreleased/26068_tasklist_issue.yml4
-rw-r--r--changelogs/unreleased/26087-asciidoc-cicd-badges-snippet.yml4
-rw-r--r--changelogs/unreleased/26117-sort-pipeline-for-commit.yml4
-rw-r--r--changelogs/unreleased/26186-diff-view-plus-and-minus-signs-as-part-of-line-number.yml4
-rw-r--r--changelogs/unreleased/26206-fix-download-dropdown.yml4
-rw-r--r--changelogs/unreleased/26286-most-recent-activity-profile-header.yml4
-rw-r--r--changelogs/unreleased/26287-link-branch-in-calendar-activity.yml4
-rw-r--r--changelogs/unreleased/26315-unify-labels-filter-behavior.yml4
-rw-r--r--changelogs/unreleased/26348-cleanup-navigation-order-groups.yml4
-rw-r--r--changelogs/unreleased/26379-iid-param.yml4
-rw-r--r--changelogs/unreleased/26445-accessible-piplelines-buttons.yml4
-rw-r--r--changelogs/unreleased/26447-fix-tab-list-order.yml4
-rw-r--r--changelogs/unreleased/26468-fix-users-sort-in-admin-area.yml4
-rw-r--r--changelogs/unreleased/26500-informative-slack-notifications.yml4
-rw-r--r--changelogs/unreleased/26651-cannot-move-project-into-group.yml4
-rw-r--r--changelogs/unreleased/26703-todos-count.yml4
-rw-r--r--changelogs/unreleased/26705-filter-todos-by-manual-add.yml4
-rw-r--r--changelogs/unreleased/26744-add-omniauth-oauth2-generic-strategy.yml3
-rw-r--r--changelogs/unreleased/26787-add-copy-icon-hover-state.yml4
-rw-r--r--changelogs/unreleased/26852-fix-slug-for-openshift.yml4
-rw-r--r--changelogs/unreleased/26863-Remove-hover-animation-from-row-elements.yml4
-rw-r--r--changelogs/unreleased/26875-builds-api-endpoint-skipped-scope.yml4
-rw-r--r--changelogs/unreleased/26900-pipelines-tabs.yml4
-rw-r--r--changelogs/unreleased/26920-hover-cursor-on-pagination-element.yml4
-rw-r--r--changelogs/unreleased/26947-build-status-self-link.yml4
-rw-r--r--changelogs/unreleased/26957-tanuki-anim-hang.yml4
-rw-r--r--changelogs/unreleased/26982-improve-pipeline-status-icon-linking-in-widgets.yml4
-rw-r--r--changelogs/unreleased/27013-regression-in-commit-title-bar.yml4
-rw-r--r--changelogs/unreleased/27014-fix-pipeline-tooltip-wrapping.yml4
-rw-r--r--changelogs/unreleased/27021-line-numbers-now-in-copy-pasta-data.yml4
-rw-r--r--changelogs/unreleased/27032-add-a-house-keeping-api-call.yml4
-rw-r--r--changelogs/unreleased/27178-update-builds-link-in-project-settings.yml4
-rw-r--r--changelogs/unreleased/27240-make-progress-bars-consistent.yml4
-rw-r--r--changelogs/unreleased/27267-unnecessary-queries-from-projects-dashboard-atom-and-json.yml4
-rw-r--r--changelogs/unreleased/27277-small-mini-pipeline-graph-glitch-upon-hover.yml4
-rw-r--r--changelogs/unreleased/27287-label-dropdown-error-messages.yml4
-rw-r--r--changelogs/unreleased/27291-unify-mr-diff-file-buttons.yml4
-rw-r--r--changelogs/unreleased/27321-double-separator-line-in-edit-projects-settings.yml4
-rw-r--r--changelogs/unreleased/27332-mini-pipeline-graph-with-many-stages-has-no-line-spacing-in-firefox-and-safari.yml4
-rw-r--r--changelogs/unreleased/27336-add-environment-url-link-to-terminal-page.yml4
-rw-r--r--changelogs/unreleased/27343-autocomplete-post-to-wrong-url-when-not-hosting-in-root.yml5
-rw-r--r--changelogs/unreleased/27352-search-label-filter-header.yml4
-rw-r--r--changelogs/unreleased/27452-update-issue-count.yml4
-rw-r--r--changelogs/unreleased/27484-environment-show-name.yml4
-rw-r--r--changelogs/unreleased/27488-fix-jwt-version.yml4
-rw-r--r--changelogs/unreleased/27494-environment-list-column-headers.yml4
-rw-r--r--changelogs/unreleased/27516-fix-wrong-call-to-project_cache_worker-method.yml4
-rw-r--r--changelogs/unreleased/27530-fix-job-dropdown-pipeline-console-error.yml4
-rw-r--r--changelogs/unreleased/27602-fix-avatar-border-flicker-mention-dropdown.yml4
-rw-r--r--changelogs/unreleased/27608-fixes-markdown-in-activity-feed-is-light-gray.yml4
-rw-r--r--changelogs/unreleased/27610-issue-number-alignment.yml4
-rw-r--r--changelogs/unreleased/27631-fix-small-height-of-activity-header-page.yml4
-rw-r--r--changelogs/unreleased/27632_fix_mr_widget_url.yml4
-rw-r--r--changelogs/unreleased/27639-emoji-panel-under-the-side-panel-in-the-merge-request.yml4
-rw-r--r--changelogs/unreleased/27726-fix-dropdown-width-in-admin-project-page.yml4
-rw-r--r--changelogs/unreleased/27762-add-default-artifacts-expiration.yml4
-rw-r--r--changelogs/unreleased/27774-text-color-contrast-is-barely-readable-for-pipelines-visualization-graph-page-with-roboto-fonts.yml4
-rw-r--r--changelogs/unreleased/27783-fix-fe-doc-broken-link.yml4
-rw-r--r--changelogs/unreleased/27822-default-bulk-assign-labels.yml4
-rw-r--r--changelogs/unreleased/27920-both-wip-messages-showing.yml4
-rw-r--r--changelogs/unreleased/27924-set-max-width-mini-pipeline-text.yml4
-rw-r--r--changelogs/unreleased/27989-disable-counting-tags.yml4
-rw-r--r--changelogs/unreleased/27994-fix-mr-widget-jump.yml4
-rw-r--r--changelogs/unreleased/28082-deleted-branch-event-404.yml4
-rw-r--r--changelogs/unreleased/28093-snippet-and-issue-spam-check-on-edit.yml4
-rw-r--r--changelogs/unreleased/28142-overlap-bugs.yml4
-rw-r--r--changelogs/unreleased/28176_merge_widget_fix.yml4
-rw-r--r--changelogs/unreleased/28186-long-group-names-overflow-out-of-todos-view.yml4
-rw-r--r--changelogs/unreleased/28204-option-to-disable-webpack-dev-server-livereload.yml4
-rw-r--r--changelogs/unreleased/28229-pipelines-loading-icon.yml5
-rw-r--r--changelogs/unreleased/28236-browse-button-dropping.yml4
-rw-r--r--changelogs/unreleased/28247-timeloops-bug.yml4
-rw-r--r--changelogs/unreleased/28253-fix-buid-scroll-button-position.yml4
-rw-r--r--changelogs/unreleased/28262-horizontal-scrolling-issue-on-long-project-names.yml4
-rw-r--r--changelogs/unreleased/28303-change-development-tanuki-favicon-colors-to-match-logo.yml4
-rw-r--r--changelogs/unreleased/28329-allow-slash-in-slash-command-args.yml4
-rw-r--r--changelogs/unreleased/28353-little-grammar-issue.yml4
-rw-r--r--changelogs/unreleased/28357-colon-search.yml4
-rw-r--r--changelogs/unreleased/28389-ux-problem-with-pipeline-coverage-placeholder.yml4
-rw-r--r--changelogs/unreleased/28450-test-compiling-frontend-assets-for-production-in-ci.yml4
-rw-r--r--changelogs/unreleased/28458-present-gitlab-version-for-v4-changes-on-docs.yml4
-rw-r--r--changelogs/unreleased/28462-fix-delimiter-removes-issue-in-todo-counter.yml4
-rw-r--r--changelogs/unreleased/28524-gitlab-ci-yml-coverage-key-is-unknown.yml4
-rw-r--r--changelogs/unreleased/3874-correctly-return-json-on-delete-responses.yml4
-rw-r--r--changelogs/unreleased/395-fix-notification-when-group-set-to-watch.yml4
-rw-r--r--changelogs/unreleased/8-15-stable.yml4
-rw-r--r--changelogs/unreleased/8082-permalink-to-file.yml4
-rw-r--r--changelogs/unreleased/9-0-api-changes.yml4
-rw-r--r--changelogs/unreleased/9381-authentiq-backchannel-logout.yml4
-rw-r--r--changelogs/unreleased/Add-a-shash-command-for-target-merge-request-branch.yml4
-rw-r--r--changelogs/unreleased/add-auto-submited-header.yml4
-rw-r--r--changelogs/unreleased/add-filtered-search-to-mr.yml4
-rw-r--r--changelogs/unreleased/add-issues-tooltip.yml4
-rw-r--r--changelogs/unreleased/add-yarn-documentation.yml4
-rw-r--r--changelogs/unreleased/add_mr_info_to_issues_list.yml4
-rw-r--r--changelogs/unreleased/add_project_update_hook.yml4
-rw-r--r--changelogs/unreleased/alphabetically_sort_tags_on_runner_list.yml4
-rw-r--r--changelogs/unreleased/api-entities.yml4
-rw-r--r--changelogs/unreleased/api-fix-files.yml4
-rw-r--r--changelogs/unreleased/api-notes-entity-fields.yml4
-rw-r--r--changelogs/unreleased/api-post-block.yml4
-rw-r--r--changelogs/unreleased/api-remove-deploy-key-disable.yml4
-rw-r--r--changelogs/unreleased/api-remove-owned-groups.yml4
-rw-r--r--changelogs/unreleased/api-remove-snippets-expires-at.yml4
-rw-r--r--changelogs/unreleased/api-star-restful.yml4
-rw-r--r--changelogs/unreleased/api-subscription-restful.yml4
-rw-r--r--changelogs/unreleased/api-todos-restful.yml4
-rw-r--r--changelogs/unreleased/artifactsdoc.yml4
-rw-r--r--changelogs/unreleased/babel-all-the-things.yml5
-rw-r--r--changelogs/unreleased/beautiful-karma-output.yml4
-rw-r--r--changelogs/unreleased/branch_deletion.yml4
-rw-r--r--changelogs/unreleased/broken_iamge_when_doing_offline_update_check-help_page-.yml4
-rw-r--r--changelogs/unreleased/bypass-email-domain-validation-when-created-by-admin.yml4
-rw-r--r--changelogs/unreleased/clipboard-button-commit-sha.yml3
-rw-r--r--changelogs/unreleased/commit-search-ui-fix.yml4
-rw-r--r--changelogs/unreleased/contribution-calendar-scroll.yml4
-rw-r--r--changelogs/unreleased/cop-gem-fetcher.yml4
-rw-r--r--changelogs/unreleased/copy-as-md.yml4
-rw-r--r--changelogs/unreleased/copy-branch-to-clipboard.yml4
-rw-r--r--changelogs/unreleased/cover-my-karma.yml4
-rw-r--r--changelogs/unreleased/create_branch_repo_less.yml4
-rw-r--r--changelogs/unreleased/disable-autologin-on-email-confirmation-links.yml4
-rw-r--r--changelogs/unreleased/display-project-id.yml4
-rw-r--r--changelogs/unreleased/document-how-to-vue.yml4
-rw-r--r--changelogs/unreleased/dont-delete-assigned-issuables.yml4
-rw-r--r--changelogs/unreleased/dynamic-header-fixture.yml4
-rw-r--r--changelogs/unreleased/dynamic-project-title-fixture.yml4
-rw-r--r--changelogs/unreleased/dz-create-nested-groups-via-ui.yml4
-rw-r--r--changelogs/unreleased/dz-nested-groups-api.yml4
-rw-r--r--changelogs/unreleased/dz-nested-groups-improvements-2.yml4
-rw-r--r--changelogs/unreleased/dz-refactor-full-path.yml4
-rw-r--r--changelogs/unreleased/empty-selection-reply-shortcut.yml4
-rw-r--r--changelogs/unreleased/fe-commit-mr-pipelines.yml4
-rw-r--r--changelogs/unreleased/fe-paginated-environments-api-add-subview.yml4
-rw-r--r--changelogs/unreleased/feature-brand-logo-in-emails.yml4
-rw-r--r--changelogs/unreleased/feature-github-find-users-by-email.yml4
-rw-r--r--changelogs/unreleased/feature-success-warning-icons-in-stages-builds.yml4
-rw-r--r--changelogs/unreleased/fix-27479.yml4
-rw-r--r--changelogs/unreleased/fix-anchor-scrolling.yml4
-rw-r--r--changelogs/unreleased/fix-api-mr-permissions.yml4
-rw-r--r--changelogs/unreleased/fix-ci-build-policy.yml4
-rw-r--r--changelogs/unreleased/fix-cycle-analytics-events-limit.yml4
-rw-r--r--changelogs/unreleased/fix-deleting-project-again.yml4
-rw-r--r--changelogs/unreleased/fix-depr-warn.yml4
-rw-r--r--changelogs/unreleased/fix-gb-backwards-compatibility-coverage-ci-yml.yml4
-rw-r--r--changelogs/unreleased/fix-gb-notification-settings-when-no-repository.yml4
-rw-r--r--changelogs/unreleased/fix-gb-pipeline-retry-builds-started.yml4
-rw-r--r--changelogs/unreleased/fix-gb-pipeline-retry-cancel-buttons-consistency.yml4
-rw-r--r--changelogs/unreleased/fix-guest-access-posting-to-notes.yml4
-rw-r--r--changelogs/unreleased/fix-import-encrypt-atts.yml4
-rw-r--r--changelogs/unreleased/fix-import-group-members.yml4
-rw-r--r--changelogs/unreleased/fix-mr-size-with-over-100-files.yml4
-rw-r--r--changelogs/unreleased/fix-references-header-parsing.yml5
-rw-r--r--changelogs/unreleased/fix-scroll-test.yml4
-rw-r--r--changelogs/unreleased/fix-users-deleting-public-deployment-keys.yml4
-rw-r--r--changelogs/unreleased/fix_broken_diff_discussions.yml4
-rw-r--r--changelogs/unreleased/fix_issue_from_milestone.yml4
-rw-r--r--changelogs/unreleased/fixes-namespace-api-documentation.yml4
-rw-r--r--changelogs/unreleased/fwn-to-find-by-full-path.yml4
-rw-r--r--changelogs/unreleased/get-rid-of-water-from-notification_service_spec-to-make-it-DRY.yml4
-rw-r--r--changelogs/unreleased/gfm-autocomplete-fixes.yml4
-rw-r--r--changelogs/unreleased/git_to_html_redirection.yml4
-rw-r--r--changelogs/unreleased/go-go-gadget-webpack.yml4
-rw-r--r--changelogs/unreleased/group-label-sidebar-link.yml4
-rw-r--r--changelogs/unreleased/group-memebrs-owner-level.yml4
-rw-r--r--changelogs/unreleased/hardcode-title-system-note.yml4
-rw-r--r--changelogs/unreleased/improve-ci-example-php-doc.yml4
-rw-r--r--changelogs/unreleased/improve-handleLocationHash-tests.yml4
-rw-r--r--changelogs/unreleased/instrument-in-karma.yml4
-rw-r--r--changelogs/unreleased/issuable-sidebar-bug.yml4
-rw-r--r--changelogs/unreleased/issue-20428.yml4
-rw-r--r--changelogs/unreleased/issue-newproj-layout.yml4
-rw-r--r--changelogs/unreleased/issue-sidebar-empty-assignee.yml4
-rw-r--r--changelogs/unreleased/issue_19262.yml4
-rw-r--r--changelogs/unreleased/issue_25112.yml4
-rw-r--r--changelogs/unreleased/issue_25900.yml4
-rw-r--r--changelogs/unreleased/issue_26701.yml4
-rw-r--r--changelogs/unreleased/issue_27211.yml4
-rw-r--r--changelogs/unreleased/issue_28051_2.yml4
-rw-r--r--changelogs/unreleased/jej-pages-picked-from-ee.yml4
-rw-r--r--changelogs/unreleased/label-promotion.yml4
-rw-r--r--changelogs/unreleased/lnovy-gitlab-ce-empty-variables.yml4
-rw-r--r--changelogs/unreleased/markdown-plantuml.yml4
-rw-r--r--changelogs/unreleased/misalinged-discussion-with-no-avatar-26854.yml4
-rw-r--r--changelogs/unreleased/move_tags_service_to_namespace.yml4
-rw-r--r--changelogs/unreleased/mr-tabs-container-offset.yml4
-rw-r--r--changelogs/unreleased/new-branch-fixture.yml4
-rw-r--r--changelogs/unreleased/newline-eslint-rule.yml4
-rw-r--r--changelogs/unreleased/no-sidebar-on-action-btn-click.yml4
-rw-r--r--changelogs/unreleased/no_project_notes.yml4
-rw-r--r--changelogs/unreleased/only-yield-valid-reference-matches.yml4
-rw-r--r--changelogs/unreleased/option-to-be-notified-of-own-activity.yml4
-rw-r--r--changelogs/unreleased/pages-0-3-2.yml4
-rw-r--r--changelogs/unreleased/paginate-all-the-things.yml4
-rw-r--r--changelogs/unreleased/pass in current_user in MergeRequest and MergeRequestsHelper.yml4
-rw-r--r--changelogs/unreleased/pass_coverage_value_to_commit_status_api.yml4
-rw-r--r--changelogs/unreleased/pms-lowercase-system-notes.yml4
-rw-r--r--changelogs/unreleased/protected-branch-dropdown-titles.yml4
-rw-r--r--changelogs/unreleased/quick-submit-fixture.yml4
-rw-r--r--changelogs/unreleased/redesign-searchbar-admin-project-26794.yml4
-rw-r--r--changelogs/unreleased/redirect-to-commit-when-only-commit-found.yml5
-rw-r--r--changelogs/unreleased/refresh-permissions-when-moving-projects.yml4
-rw-r--r--changelogs/unreleased/relative-url-assets.yml4
-rw-r--r--changelogs/unreleased/removal_of_unused_parameter.yml4
-rw-r--r--changelogs/unreleased/remove-deploy-key-endpoint.yml4
-rw-r--r--changelogs/unreleased/remove-inactive-default-email-services.yml4
-rw-r--r--changelogs/unreleased/remove-jquery-ui-datepicker.yml4
-rw-r--r--changelogs/unreleased/remove-jquery-ui-sortable.yml4
-rw-r--r--changelogs/unreleased/remove-sidekiq-backup-ar-threads.yml4
-rw-r--r--changelogs/unreleased/rename-retry-failed-pipeline-to-retry.yml4
-rw-r--r--changelogs/unreleased/rename_delete_services.yml4
-rw-r--r--changelogs/unreleased/rename_files_delete_service.yml4
-rw-r--r--changelogs/unreleased/replace-npm-with-yarn.yml4
-rw-r--r--changelogs/unreleased/requires-input-fixture.yml4
-rw-r--r--changelogs/unreleased/route-map.yml4
-rw-r--r--changelogs/unreleased/seed-abuse-reports.yml4
-rw-r--r--changelogs/unreleased/sh-delete-user-permission-check.yml4
-rw-r--r--changelogs/unreleased/slash-commands-typo.yml4
-rw-r--r--changelogs/unreleased/small-screen-fullscreen-button.yml4
-rw-r--r--changelogs/unreleased/snippets-search.yml4
-rw-r--r--changelogs/unreleased/static-navbar.yml4
-rw-r--r--changelogs/unreleased/task_list_refactor.yml4
-rw-r--r--changelogs/unreleased/tc-only-mr-button-if-allowed.yml4
-rw-r--r--changelogs/unreleased/terminal-max-session-time.yml4
-rw-r--r--changelogs/unreleased/upgrade-omniauth.yml4
-rw-r--r--changelogs/unreleased/wip-mr-from-commits.yml4
-rw-r--r--changelogs/unreleased/zj-fix-slash-command-labels.yml4
-rw-r--r--changelogs/unreleased/zj-format-chat-messages.yml4
-rw-r--r--changelogs/unreleased/zj-requeue-pending-delete.yml4
-rw-r--r--config/dependency_decisions.yml24
-rw-r--r--config/gitlab.yml.example10
-rw-r--r--config/initializers/1_settings.rb39
-rw-r--r--config/initializers/4_ci_app.rb8
-rw-r--r--config/initializers/acts_as_taggable.rb5
-rw-r--r--config/initializers/additional_headers_interceptor.rb1
-rw-r--r--config/initializers/devise.rb19
-rw-r--r--config/initializers/gollum.rb2
-rw-r--r--config/initializers/health_check.rb4
-rw-r--r--config/initializers/metrics.rb14
-rw-r--r--config/initializers/mysql_ignore_postgresql_options.rb2
-rw-r--r--config/initializers/rack_lineprof.rb2
-rw-r--r--config/initializers/rspec_profiling.rb8
-rw-r--r--config/initializers/trusted_proxies.rb2
-rw-r--r--config/initializers/workhorse_multipart.rb2
-rw-r--r--config/karma.config.js19
-rw-r--r--config/mail_room.yml5
-rw-r--r--config/routes/dashboard.rb3
-rw-r--r--config/routes/group.rb1
-rw-r--r--config/routes/project.rb4
-rw-r--r--config/routes/sidekiq.rb2
-rw-r--r--config/routes/wiki.rb2
-rw-r--r--config/sidekiq_queues.yml2
-rw-r--r--config/webpack.config.js65
-rw-r--r--db/fixtures/development/18_abuse_reports.rb5
-rw-r--r--db/migrate/20160519203051_add_developers_can_merge_to_protected_branches.rb8
-rw-r--r--db/migrate/20160610201627_migrate_users_notification_level.rb2
-rw-r--r--db/migrate/20160801163709_add_submitted_as_ham_to_spam_logs.rb6
-rw-r--r--db/migrate/20160829114652_add_markdown_cache_columns.rb2
-rw-r--r--db/migrate/20160831214543_migrate_project_features.rb2
-rw-r--r--db/migrate/20160919145149_add_group_id_to_labels.rb2
-rw-r--r--db/migrate/20161019190736_migrate_sidekiq_queues_from_default.rb4
-rw-r--r--db/migrate/20161020083353_add_pipeline_id_to_merge_request_metrics.rb2
-rw-r--r--db/migrate/20161024042317_migrate_mailroom_queue_from_default.rb4
-rw-r--r--db/migrate/20161031171301_add_project_id_to_subscriptions.rb2
-rw-r--r--db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb4
-rw-r--r--db/migrate/20161128142110_remove_unnecessary_indexes.rb4
-rw-r--r--db/migrate/20161207231620_fixup_environment_name_uniqueness.rb8
-rw-r--r--db/migrate/20170123061730_add_notified_of_own_activity_to_users.rb14
-rw-r--r--db/migrate/20170204172458_add_name_to_route.rb12
-rw-r--r--db/migrate/20170206115204_add_column_ghost_to_users.rb11
-rw-r--r--db/migrate/20170210062829_add_index_to_labels_for_title_and_project.rb12
-rw-r--r--db/migrate/20170210075922_add_index_to_ci_trigger_requests_for_commit_id.rb11
-rw-r--r--db/migrate/20170210103609_add_index_to_user_agent_detail.rb14
-rw-r--r--db/migrate/20170214084746_add_default_artifacts_expiration_to_application_settings.rb11
-rw-r--r--db/migrate/20170216135621_add_index_for_latest_successful_pipeline.rb10
-rw-r--r--db/migrate/20170216141440_drop_index_for_builds_project_status.rb8
-rw-r--r--db/post_migrate/20161109150329_fix_project_records_with_invalid_visibility.rb14
-rw-r--r--db/post_migrate/20161221153951_rename_reserved_project_names.rb2
-rw-r--r--db/post_migrate/20170206040400_remove_inactive_default_email_services.rb41
-rw-r--r--db/post_migrate/20170211073944_disable_invalid_service_templates.rb15
-rw-r--r--db/post_migrate/20170214111112_delete_deprecated_gitlab_ci_service.rb15
-rw-r--r--db/post_migrate/20170215200045_remove_theme_id_from_users.rb9
-rw-r--r--db/schema.rb17
-rw-r--r--doc/README.md4
-rw-r--r--doc/administration/auth/authentiq.md2
-rw-r--r--doc/administration/build_artifacts.md97
-rw-r--r--doc/administration/job_artifacts.md114
-rw-r--r--doc/administration/monitoring/performance/introduction.md2
-rw-r--r--doc/administration/monitoring/performance/prometheus.md103
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_monitor_exporter.md30
-rw-r--r--doc/administration/monitoring/prometheus/index.md147
-rw-r--r--doc/administration/monitoring/prometheus/node_exporter.md30
-rw-r--r--doc/administration/monitoring/prometheus/postgres_exporter.md30
-rw-r--r--doc/administration/monitoring/prometheus/redis_exporter.md33
-rw-r--r--doc/administration/pages/index.md181
-rw-r--r--doc/administration/pages/source.md257
-rw-r--r--doc/administration/reply_by_email.md4
-rw-r--r--doc/api/branches.md14
-rw-r--r--doc/api/commits.md28
-rw-r--r--doc/api/deploy_keys.md39
-rw-r--r--doc/api/groups.md77
-rw-r--r--doc/api/issues.md12
-rw-r--r--doc/api/keys.md1
-rw-r--r--doc/api/labels.md10
-rw-r--r--doc/api/merge_requests.md17
-rw-r--r--doc/api/milestones.md13
-rw-r--r--doc/api/namespaces.md9
-rw-r--r--doc/api/notes.md18
-rw-r--r--doc/api/oauth2.md6
-rw-r--r--doc/api/pipelines.md2
-rw-r--r--doc/api/projects.md259
-rw-r--r--doc/api/repository_files.md24
-rw-r--r--doc/api/session.md1
-rw-r--r--doc/api/todos.md15
-rw-r--r--doc/api/users.md20
-rw-r--r--doc/api/v3_to_v4.md47
-rw-r--r--doc/ci/README.md18
-rw-r--r--doc/ci/build_artifacts/README.md5
-rw-r--r--doc/ci/docker/README.md4
-rw-r--r--doc/ci/docker/using_docker_build.md96
-rw-r--r--doc/ci/docker/using_docker_images.md35
-rw-r--r--doc/ci/enable_or_disable_ci.md18
-rw-r--r--doc/ci/environments.md15
-rw-r--r--doc/ci/examples/deployment/README.md2
-rw-r--r--doc/ci/examples/deployment/composer-npm-deploy.md4
-rw-r--r--doc/ci/examples/php.md12
-rw-r--r--doc/ci/examples/test-scala-application.md8
-rw-r--r--doc/ci/git_submodules.md18
-rw-r--r--doc/ci/img/features_settings.pngbin9243 -> 0 bytes
-rw-r--r--doc/ci/img/permissions_settings.pngbin0 -> 39194 bytes
-rw-r--r--doc/ci/img/pipelines-goal.svg4
-rw-r--r--doc/ci/img/types-of-pipelines.svg4
-rw-r--r--doc/ci/pipelines.md51
-rw-r--r--doc/ci/quick_start/README.md78
-rw-r--r--doc/ci/quick_start/img/build_log.pngbin24461 -> 35261 bytes
-rw-r--r--doc/ci/quick_start/img/builds_status.pngbin24278 -> 19127 bytes
-rw-r--r--doc/ci/quick_start/img/new_commit.pngbin4772 -> 5584 bytes
-rw-r--r--doc/ci/quick_start/img/pipelines_status.pngbin25494 -> 22872 bytes
-rw-r--r--doc/ci/quick_start/img/runners_activated.pngbin12337 -> 18215 bytes
-rw-r--r--doc/ci/quick_start/img/single_commit_status_pending.pngbin15785 -> 13631 bytes
-rw-r--r--doc/ci/quick_start/img/status_pending.pngbin9521 -> 0 bytes
-rw-r--r--doc/ci/runners/README.md132
-rw-r--r--doc/ci/services/mysql.md4
-rw-r--r--doc/ci/services/postgres.md4
-rw-r--r--doc/ci/services/redis.md2
-rw-r--r--doc/ci/ssh_keys/README.md6
-rw-r--r--doc/ci/triggers/README.md59
-rw-r--r--doc/ci/triggers/img/builds_page.pngbin29044 -> 20383 bytes
-rw-r--r--doc/ci/triggers/img/trigger_single_build.pngbin8233 -> 6585 bytes
-rw-r--r--doc/ci/triggers/img/trigger_variables.pngbin3652 -> 3637 bytes
-rw-r--r--doc/ci/triggers/img/triggers_page.pngbin5119 -> 5116 bytes
-rw-r--r--doc/ci/variables/README.md63
-rw-r--r--doc/ci/yaml/README.md301
-rw-r--r--doc/customization/branded_page_and_email_header.md15
-rw-r--r--doc/customization/branded_page_and_email_header/appearance.pngbin0 -> 10253 bytes
-rw-r--r--doc/customization/branded_page_and_email_header/custom_brand_header.pngbin0 -> 10014 bytes
-rw-r--r--doc/customization/branded_page_and_email_header/custom_email_header.pngbin0 -> 37472 bytes
-rw-r--r--doc/development/code_review.md2
-rw-r--r--doc/development/frontend.md104
-rw-r--r--doc/development/gotchas.md8
-rw-r--r--doc/development/licensing.md7
-rw-r--r--doc/development/limit_ee_conflicts.md25
-rw-r--r--doc/development/rake_tasks.md10
-rw-r--r--doc/development/testing.md34
-rw-r--r--doc/development/ux_guide/copy.md372
-rw-r--r--doc/development/ux_guide/img/copy-form-addissuebutton.pngbin16085 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/copy-form-addissueform.pngbin25978 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/copy-form-editissuebutton.pngbin11801 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/copy-form-editissueform.pngbin25621 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/harry-robison.pngbin0 -> 10712 bytes
-rw-r--r--doc/development/ux_guide/img/james-mackey.pngbin0 -> 11147 bytes
-rw-r--r--doc/development/ux_guide/img/steven-lyons.pngbin0 -> 9323 bytes
-rw-r--r--doc/development/ux_guide/users.md170
-rw-r--r--doc/gitlab-basics/command-line-commands.md2
-rw-r--r--doc/gitlab-basics/img/profile_settings.pngbin3045 -> 5842 bytes
-rw-r--r--doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.pngbin8133 -> 24639 bytes
-rw-r--r--doc/install/database_mysql.md79
-rw-r--r--doc/install/installation.md35
-rw-r--r--doc/install/requirements.md15
-rw-r--r--doc/integration/auth0.md7
-rw-r--r--doc/integration/azure.md6
-rw-r--r--doc/integration/cas.md9
-rw-r--r--doc/integration/crowd.md9
-rw-r--r--doc/integration/facebook.md6
-rw-r--r--doc/integration/github.md22
-rw-r--r--doc/integration/gitlab.md16
-rw-r--r--doc/integration/google.md6
-rw-r--r--doc/integration/ldap.md4
-rw-r--r--doc/integration/oauth2_generic.md65
-rw-r--r--doc/integration/omniauth.md1
-rw-r--r--doc/integration/saml.md16
-rw-r--r--doc/integration/shibboleth.md12
-rw-r--r--doc/integration/twitter.md6
-rw-r--r--doc/pages/README.md1
-rw-r--r--doc/pages/getting_started_part_one.md266
-rw-r--r--doc/pages/getting_started_part_three.md383
-rw-r--r--doc/pages/getting_started_part_two.md152
-rw-r--r--doc/pages/img/add_certificate_to_pages.pngbin0 -> 14608 bytes
-rw-r--r--doc/pages/img/choose_ci_template.pngbin0 -> 23532 bytes
-rw-r--r--doc/pages/img/dns_a_record_example.pngbin0 -> 4709 bytes
-rw-r--r--doc/pages/img/dns_cname_record_example.pngbin0 -> 5004 bytes
-rw-r--r--doc/pages/img/remove_fork_relashionship.pngbin0 -> 13646 bytes
-rw-r--r--doc/pages/img/setup_ci.pngbin0 -> 10033 bytes
-rw-r--r--doc/pages/index.md49
-rw-r--r--doc/profile/preferences.md7
-rw-r--r--doc/raketasks/backup_restore.md26
-rw-r--r--doc/university/README.md2
-rw-r--r--doc/university/glossary/README.md74
-rw-r--r--doc/update/7.0-to-7.1.md2
-rw-r--r--doc/update/7.1-to-7.2.md2
-rw-r--r--doc/update/7.10-to-7.11.md4
-rw-r--r--doc/update/7.11-to-7.12.md4
-rw-r--r--doc/update/7.12-to-7.13.md4
-rw-r--r--doc/update/7.13-to-7.14.md4
-rw-r--r--doc/update/7.14-to-8.0.md4
-rw-r--r--doc/update/7.2-to-7.3.md2
-rw-r--r--doc/update/7.3-to-7.4.md7
-rw-r--r--doc/update/7.4-to-7.5.md8
-rw-r--r--doc/update/7.5-to-7.6.md13
-rw-r--r--doc/update/7.6-to-7.7.md15
-rw-r--r--doc/update/7.7-to-7.8.md13
-rw-r--r--doc/update/7.8-to-7.9.md13
-rw-r--r--doc/update/7.9-to-7.10.md13
-rw-r--r--doc/update/8.0-to-8.1.md4
-rw-r--r--doc/update/8.1-to-8.2.md4
-rw-r--r--doc/update/8.10-to-8.11.md4
-rw-r--r--doc/update/8.11-to-8.12.md4
-rw-r--r--doc/update/8.12-to-8.13.md4
-rw-r--r--doc/update/8.13-to-8.14.md4
-rw-r--r--doc/update/8.14-to-8.15.md4
-rw-r--r--doc/update/8.15-to-8.16.md4
-rw-r--r--doc/update/8.16-to-8.17.md35
-rw-r--r--doc/update/8.2-to-8.3.md4
-rw-r--r--doc/update/8.3-to-8.4.md6
-rw-r--r--doc/update/8.4-to-8.5.md6
-rw-r--r--doc/update/8.5-to-8.6.md4
-rw-r--r--doc/update/8.6-to-8.7.md4
-rw-r--r--doc/update/8.7-to-8.8.md4
-rw-r--r--doc/update/8.8-to-8.9.md4
-rw-r--r--doc/update/8.9-to-8.10.md4
-rw-r--r--doc/user/account/security.md2
-rw-r--r--doc/user/admin_area/settings/continuous_integration.md28
-rw-r--r--doc/user/admin_area/settings/img/admin_area_default_artifacts_expiration.pngbin0 -> 14656 bytes
-rw-r--r--doc/user/admin_area/settings/img/admin_area_maximum_artifacts_size.pngbin3447 -> 12917 bytes
-rw-r--r--doc/user/admin_area/settings/sign_up_restrictions.md24
-rw-r--r--doc/user/markdown.md66
-rw-r--r--doc/user/permissions.md22
-rw-r--r--doc/user/profile/account/two_factor_authentication.md2
-rw-r--r--doc/user/project/builds/artifacts.md137
-rw-r--r--doc/user/project/builds/img/build_artifacts_browser.pngbin3782 -> 0 bytes
-rw-r--r--doc/user/project/builds/img/build_artifacts_browser_button.pngbin4891 -> 0 bytes
-rw-r--r--doc/user/project/builds/img/build_artifacts_builds_page.pngbin22022 -> 0 bytes
-rw-r--r--doc/user/project/builds/img/build_artifacts_pipelines_page.pngbin28339 -> 0 bytes
-rw-r--r--doc/user/project/img/issue_board.pngbin90664 -> 76461 bytes
-rw-r--r--doc/user/project/img/issue_board_search_backlog.pngbin9769 -> 0 bytes
-rw-r--r--doc/user/project/img/issue_board_welcome_message.pngbin97419 -> 120751 bytes
-rw-r--r--doc/user/project/img/issue_boards_add_issues_modal.pngbin0 -> 177057 bytes
-rw-r--r--doc/user/project/img/issue_boards_remove_issue.pngbin0 -> 135168 bytes
-rw-r--r--doc/user/project/img/protected_branches_devs_can_push.pngbin8302 -> 34888 bytes
-rw-r--r--doc/user/project/integrations/img/services_templates_redmine_example.pngbin8776 -> 8608 bytes
-rw-r--r--doc/user/project/integrations/services_templates.md19
-rw-r--r--doc/user/project/integrations/webhooks.md4
-rw-r--r--doc/user/project/issue_board.md51
-rw-r--r--doc/user/project/merge_requests.md170
-rw-r--r--doc/user/project/merge_requests/index.md169
-rw-r--r--doc/user/project/merge_requests/merge_when_pipeline_succeeds.md12
-rw-r--r--doc/user/project/merge_requests/versions.md17
-rw-r--r--doc/user/project/new_ci_build_permissions_model.md82
-rw-r--r--doc/user/project/pages/index.md46
-rw-r--r--doc/user/project/pipelines/img/job_artifacts_browser.pngbin0 -> 3771 bytes
-rw-r--r--doc/user/project/pipelines/img/job_artifacts_browser_button.pngbin0 -> 5534 bytes
-rw-r--r--doc/user/project/pipelines/img/job_artifacts_builds_page.pngbin0 -> 15191 bytes
-rw-r--r--doc/user/project/pipelines/img/job_artifacts_pipelines_page.pngbin0 -> 16550 bytes
-rw-r--r--doc/user/project/pipelines/img/job_latest_artifacts_browser.png (renamed from doc/user/project/builds/img/build_latest_artifacts_browser.png)bin10551 -> 10551 bytes
-rw-r--r--doc/user/project/pipelines/img/pipelines_settings_test_coverage.pngbin2603 -> 2549 bytes
-rw-r--r--doc/user/project/pipelines/img/pipelines_test_coverage_mr_widget.pngbin6391 -> 6375 bytes
-rw-r--r--doc/user/project/pipelines/job_artifacts.md143
-rw-r--r--doc/user/project/pipelines/settings.md16
-rw-r--r--doc/user/project/slash_commands.md2
-rw-r--r--doc/user/snippets.md19
-rw-r--r--doc/workflow/README.md3
-rw-r--r--doc/workflow/gitlab_flow.md2
-rw-r--r--doc/workflow/groups.md2
-rw-r--r--doc/workflow/importing/import_projects_from_github.md4
-rw-r--r--doc/workflow/shortcuts.md4
-rw-r--r--doc/workflow/todos.md33
-rw-r--r--features/dashboard/issues.feature21
-rw-r--r--features/project/labels.feature15
-rw-r--r--features/project/merge_requests.feature7
-rw-r--r--features/project/merge_requests/revert.feature1
-rw-r--r--features/snippets/user.feature34
-rw-r--r--features/steps/dashboard/issues.rb91
-rw-r--r--features/steps/dashboard/todos.rb23
-rw-r--r--features/steps/group/milestones.rb4
-rw-r--r--features/steps/project/builds/artifacts.rb2
-rw-r--r--features/steps/project/labels.rb32
-rw-r--r--features/steps/shared/builds.rb2
-rw-r--r--features/steps/shared/issuable.rb2
-rw-r--r--features/steps/snippets/user.rb55
-rw-r--r--lib/additional_email_headers_interceptor.rb8
-rw-r--r--lib/api/api.rb17
-rw-r--r--lib/api/api_guard.rb6
-rw-r--r--lib/api/award_emoji.rb9
-rw-r--r--lib/api/boards.rb13
-rw-r--r--lib/api/branches.rb20
-rw-r--r--lib/api/builds.rb2
-rw-r--r--lib/api/commit_statuses.rb6
-rw-r--r--lib/api/commits.rb18
-rw-r--r--lib/api/deploy_keys.rb36
-rw-r--r--lib/api/entities.rb50
-rw-r--r--lib/api/files.rb11
-rw-r--r--lib/api/groups.rb22
-rw-r--r--lib/api/helpers.rb36
-rw-r--r--lib/api/helpers/pagination.rb2
-rw-r--r--lib/api/issues.rb20
-rw-r--r--lib/api/labels.rb8
-rw-r--r--lib/api/members.rb20
-rw-r--r--lib/api/merge_request_diffs.rb6
-rw-r--r--lib/api/merge_requests.rb5
-rw-r--r--lib/api/milestones.rb22
-rw-r--r--lib/api/notes.rb6
-rw-r--r--lib/api/pagination_params.rb4
-rw-r--r--lib/api/pipelines.rb6
-rw-r--r--lib/api/project_hooks.rb4
-rw-r--r--lib/api/project_snippets.rb8
-rw-r--r--lib/api/projects.rb100
-rw-r--r--lib/api/repositories.rb16
-rw-r--r--lib/api/runners.rb5
-rw-r--r--lib/api/settings.rb7
-rw-r--r--lib/api/snippets.rb7
-rw-r--r--lib/api/subscriptions.rb5
-rw-r--r--lib/api/system_hooks.rb10
-rw-r--r--lib/api/tags.rb14
-rw-r--r--lib/api/templates.rb103
-rw-r--r--lib/api/todos.rb8
-rw-r--r--lib/api/triggers.rb11
-rw-r--r--lib/api/users.rb40
-rw-r--r--lib/api/v3/boards.rb51
-rw-r--r--lib/api/v3/branches.rb31
-rw-r--r--lib/api/v3/commits.rb205
-rw-r--r--lib/api/v3/entities.rb34
-rw-r--r--lib/api/v3/files.rb138
-rw-r--r--lib/api/v3/groups.rb38
-rw-r--r--lib/api/v3/issues.rb13
-rw-r--r--lib/api/v3/labels.rb19
-rw-r--r--lib/api/v3/members.rb133
-rw-r--r--lib/api/v3/notes.rb148
-rw-r--r--lib/api/v3/project_snippets.rb8
-rw-r--r--lib/api/v3/projects.rb7
-rw-r--r--lib/api/v3/repositories.rb55
-rw-r--r--lib/api/v3/subscriptions.rb53
-rw-r--r--lib/api/v3/system_hooks.rb19
-rw-r--r--lib/api/v3/tags.rb20
-rw-r--r--lib/api/v3/templates.rb122
-rw-r--r--lib/api/v3/todos.rb28
-rw-r--r--lib/api/v3/users.rb117
-rw-r--r--lib/backup/database.rb70
-rw-r--r--lib/backup/files.rb21
-rw-r--r--lib/backup/manager.rb26
-rw-r--r--lib/backup/repository.rb17
-rw-r--r--lib/backup/uploads.rb1
-rw-r--r--lib/banzai/filter/abstract_reference_filter.rb20
-rw-r--r--lib/banzai/filter/autolink_filter.rb2
-rw-r--r--lib/banzai/filter/gollum_tags_filter.rb11
-rw-r--r--lib/banzai/filter/issue_reference_filter.rb11
-rw-r--r--lib/banzai/filter/plantuml_filter.rb2
-rw-r--r--lib/banzai/filter/user_reference_filter.rb6
-rw-r--r--lib/banzai/querying.rb56
-rw-r--r--lib/banzai/reference_extractor.rb5
-rw-r--r--lib/banzai/reference_parser/base_parser.rb5
-rw-r--r--lib/banzai/reference_parser/directly_addressed_user_parser.rb8
-rw-r--r--lib/bitbucket/connection.rb10
-rw-r--r--lib/bitbucket/representation/repo.rb2
-rw-r--r--lib/ci/ansi2html.rb52
-rw-r--r--lib/ci/api/builds.rb5
-rw-r--r--lib/ci/api/helpers.rb6
-rw-r--r--lib/ci/api/runners.rb44
-rw-r--r--lib/ci/api/triggers.rb43
-rw-r--r--lib/container_registry/client.rb2
-rw-r--r--lib/extracts_path.rb2
-rw-r--r--lib/file_size_validator.rb4
-rw-r--r--lib/gitlab/access.rb4
-rw-r--r--lib/gitlab/asciidoc.rb3
-rw-r--r--lib/gitlab/auth.rb4
-rw-r--r--lib/gitlab/award_emoji.rb11
-rw-r--r--lib/gitlab/badge/build/template.rb2
-rw-r--r--lib/gitlab/badge/coverage/template.rb2
-rw-r--r--lib/gitlab/badge/metadata.rb4
-rw-r--r--lib/gitlab/changes_list.rb2
-rw-r--r--lib/gitlab/chat_commands/presenters/issue_base.rb (renamed from lib/gitlab/chat_commands/presenters/issuable.rb)4
-rw-r--r--lib/gitlab/chat_commands/presenters/issue_new.rb6
-rw-r--r--lib/gitlab/chat_commands/presenters/issue_search.rb2
-rw-r--r--lib/gitlab/chat_commands/presenters/issue_show.rb2
-rw-r--r--lib/gitlab/ci/build/artifacts/metadata/entry.rb6
-rw-r--r--lib/gitlab/ci/config/entry/artifacts.rb2
-rw-r--r--lib/gitlab/ci/config/entry/cache.rb2
-rw-r--r--lib/gitlab/ci/config/entry/configurable.rb2
-rw-r--r--lib/gitlab/ci/config/entry/environment.rb8
-rw-r--r--lib/gitlab/ci/config/entry/job.rb2
-rw-r--r--lib/gitlab/conflict/file.rb11
-rw-r--r--lib/gitlab/contributions_calendar.rb6
-rw-r--r--lib/gitlab/current_settings.rb4
-rw-r--r--lib/gitlab/cycle_analytics/base_event_fetcher.rb4
-rw-r--r--lib/gitlab/cycle_analytics/code_stage.rb4
-rw-r--r--lib/gitlab/cycle_analytics/issue_stage.rb4
-rw-r--r--lib/gitlab/cycle_analytics/plan_stage.rb4
-rw-r--r--lib/gitlab/cycle_analytics/production_stage.rb4
-rw-r--r--lib/gitlab/cycle_analytics/review_stage.rb4
-rw-r--r--lib/gitlab/cycle_analytics/staging_stage.rb4
-rw-r--r--lib/gitlab/cycle_analytics/test_stage.rb4
-rw-r--r--lib/gitlab/data_builder/build.rb10
-rw-r--r--lib/gitlab/database.rb32
-rw-r--r--lib/gitlab/database/median.rb1
-rw-r--r--lib/gitlab/database/migration_helpers.rb59
-rw-r--r--lib/gitlab/diff/highlight.rb2
-rw-r--r--lib/gitlab/diff/inline_diff_marker.rb2
-rw-r--r--lib/gitlab/diff/parser.rb4
-rw-r--r--lib/gitlab/diff/position.rb19
-rw-r--r--lib/gitlab/downtime_check/message.rb4
-rw-r--r--lib/gitlab/ee_compat_check.rb2
-rw-r--r--lib/gitlab/email/handler.rb2
-rw-r--r--lib/gitlab/email/message/repository_push.rb4
-rw-r--r--lib/gitlab/email/reply_parser.rb11
-rw-r--r--lib/gitlab/exclusive_lease.rb2
-rw-r--r--lib/gitlab/file_detector.rb2
-rw-r--r--lib/gitlab/git/blob_snippet.rb2
-rw-r--r--lib/gitlab/git/commit.rb6
-rw-r--r--lib/gitlab/git/repository.rb18
-rw-r--r--lib/gitlab/git_access.rb6
-rw-r--r--lib/gitlab/github_import/base_formatter.rb18
-rw-r--r--lib/gitlab/github_import/client.rb8
-rw-r--r--lib/gitlab/github_import/comment_formatter.rb10
-rw-r--r--lib/gitlab/github_import/importer.rb21
-rw-r--r--lib/gitlab/github_import/issuable_formatter.rb30
-rw-r--r--lib/gitlab/github_import/user_formatter.rb45
-rw-r--r--lib/gitlab/gon_helper.rb2
-rw-r--r--lib/gitlab/google_code_import/importer.rb2
-rw-r--r--lib/gitlab/import_export.rb4
-rw-r--r--lib/gitlab/import_export/command_line_util.rb12
-rw-r--r--lib/gitlab/import_export/importer.rb2
-rw-r--r--lib/gitlab/import_export/repo_restorer.rb21
-rw-r--r--lib/gitlab/ldap/person.rb4
-rw-r--r--lib/gitlab/metrics.rb6
-rw-r--r--lib/gitlab/metrics/instrumentation.rb11
-rw-r--r--lib/gitlab/metrics/rack_middleware.rb6
-rw-r--r--lib/gitlab/metrics/subscribers/action_view.rb2
-rw-r--r--lib/gitlab/metrics/system.rb2
-rw-r--r--lib/gitlab/metrics/transaction.rb2
-rw-r--r--lib/gitlab/middleware/multipart.rb2
-rw-r--r--lib/gitlab/o_auth/user.rb11
-rw-r--r--lib/gitlab/optimistic_locking.rb6
-rw-r--r--lib/gitlab/other_markup.rb3
-rw-r--r--lib/gitlab/redis.rb21
-rw-r--r--lib/gitlab/reference_extractor.rb8
-rw-r--r--lib/gitlab/regex.rb13
-rw-r--r--lib/gitlab/request_profiler.rb2
-rw-r--r--lib/gitlab/saml/user.rb11
-rw-r--r--lib/gitlab/sanitizers/svg/whitelist.rb25
-rw-r--r--lib/gitlab/search_results.rb22
-rw-r--r--lib/gitlab/seeder.rb2
-rw-r--r--lib/gitlab/serializer/ci/variables.rb (renamed from lib/gitlab/serialize/ci/variables.rb)2
-rw-r--r--lib/gitlab/serializer/pagination.rb36
-rw-r--r--lib/gitlab/shell.rb10
-rw-r--r--lib/gitlab/sherlock/query.rb11
-rw-r--r--lib/gitlab/sidekiq_status/client_middleware.rb2
-rw-r--r--lib/gitlab/sidekiq_status/server_middleware.rb2
-rw-r--r--lib/gitlab/slash_commands/extractor.rb2
-rw-r--r--lib/gitlab/snippet_search_results.rb4
-rw-r--r--lib/gitlab/template/gitlab_ci_yml_template.rb2
-rw-r--r--lib/gitlab/themes.rb87
-rw-r--r--lib/gitlab/upgrader.rb15
-rw-r--r--lib/gitlab/url_sanitizer.rb2
-rw-r--r--lib/gitlab/visibility_level.rb4
-rw-r--r--lib/gitlab/workhorse.rb8
-rw-r--r--lib/tasks/brakeman.rake2
-rw-r--r--lib/tasks/cache.rake2
-rw-r--r--lib/tasks/dev.rake2
-rw-r--r--lib/tasks/downtime_check.rake10
-rw-r--r--lib/tasks/eslint.rake7
-rw-r--r--lib/tasks/flay.rake2
-rw-r--r--lib/tasks/gitlab/assets.rake24
-rw-r--r--lib/tasks/gitlab/check.rake65
-rw-r--r--lib/tasks/gitlab/cleanup.rake11
-rw-r--r--lib/tasks/gitlab/db.rake8
-rw-r--r--lib/tasks/gitlab/git.rake2
-rw-r--r--lib/tasks/gitlab/import.rake2
-rw-r--r--lib/tasks/gitlab/import_export.rake2
-rw-r--r--lib/tasks/gitlab/info.rake19
-rw-r--r--lib/tasks/gitlab/shell.rake6
-rw-r--r--lib/tasks/gitlab/sidekiq.rake6
-rw-r--r--lib/tasks/gitlab/task_helpers.rb49
-rw-r--r--lib/tasks/gitlab/test.rake12
-rw-r--r--lib/tasks/gitlab/track_deployment.rake4
-rw-r--r--lib/tasks/gitlab/update_templates.rake2
-rw-r--r--lib/tasks/gitlab/web_hook.rake6
-rw-r--r--lib/tasks/karma.rake11
-rw-r--r--lib/tasks/lint.rake1
-rw-r--r--lib/tasks/migrate/migrate_iids.rake2
-rw-r--r--lib/tasks/services.rake10
-rw-r--r--lib/tasks/sidekiq.rake8
-rw-r--r--lib/tasks/spec.rake30
-rw-r--r--lib/tasks/spinach.rake2
-rw-r--r--lib/tasks/yarn.rake40
-rw-r--r--package.json63
-rw-r--r--rubocop/cop/gem_fetcher.rb27
-rw-r--r--rubocop/cop/migration/add_column.rb (renamed from rubocop/cop/migration/column_with_default.rb)10
-rw-r--r--rubocop/cop/migration/add_column_with_default.rb34
-rw-r--r--rubocop/cop/migration/add_concurrent_foreign_key.rb27
-rw-r--r--rubocop/cop/migration/add_index.rb4
-rw-r--r--rubocop/rubocop.rb7
-rw-r--r--spec/config/mail_room_spec.rb4
-rw-r--r--spec/controllers/admin/runners_controller_spec.rb85
-rw-r--r--spec/controllers/blob_controller_spec.rb8
-rw-r--r--spec/controllers/dashboard/todos_controller_spec.rb25
-rw-r--r--spec/controllers/dashboard_controller_spec.rb19
-rw-r--r--spec/controllers/profiles/keys_controller_spec.rb19
-rw-r--r--spec/controllers/profiles/notifications_controller_spec.rb45
-rw-r--r--spec/controllers/profiles/personal_access_tokens_spec.rb4
-rw-r--r--spec/controllers/profiles/preferences_controller_spec.rb6
-rw-r--r--spec/controllers/projects/blame_controller_spec.rb4
-rw-r--r--spec/controllers/projects/blob_controller_spec.rb73
-rw-r--r--spec/controllers/projects/boards/issues_controller_spec.rb6
-rw-r--r--spec/controllers/projects/boards/lists_controller_spec.rb10
-rw-r--r--spec/controllers/projects/boards_controller_spec.rb8
-rw-r--r--spec/controllers/projects/branches_controller_spec.rb67
-rw-r--r--spec/controllers/projects/commit_controller_spec.rb52
-rw-r--r--spec/controllers/projects/commits_controller_spec.rb8
-rw-r--r--spec/controllers/projects/compare_controller_spec.rb32
-rw-r--r--spec/controllers/projects/cycle_analytics_controller_spec.rb8
-rw-r--r--spec/controllers/projects/environments_controller_spec.rb65
-rw-r--r--spec/controllers/projects/find_file_controller_spec.rb8
-rw-r--r--spec/controllers/projects/forks_controller_spec.rb12
-rw-r--r--spec/controllers/projects/graphs_controller_spec.rb2
-rw-r--r--spec/controllers/projects/group_links_controller_spec.rb12
-rw-r--r--spec/controllers/projects/imports_controller_spec.rb18
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb165
-rw-r--r--spec/controllers/projects/labels_controller_spec.rb16
-rw-r--r--spec/controllers/projects/mattermosts_controller_spec.rb4
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb83
-rw-r--r--spec/controllers/projects/pipelines_controller_spec.rb21
-rw-r--r--spec/controllers/projects/protected_branches_controller_spec.rb2
-rw-r--r--spec/controllers/projects/raw_controller_spec.rb8
-rw-r--r--spec/controllers/projects/refs_controller_spec.rb4
-rw-r--r--spec/controllers/projects/releases_controller_spec.rb6
-rw-r--r--spec/controllers/projects/repositories_controller_spec.rb6
-rw-r--r--spec/controllers/projects/runners_controller_spec.rb75
-rw-r--r--spec/controllers/projects/snippets_controller_spec.rb249
-rw-r--r--spec/controllers/projects/tags_controller_spec.rb4
-rw-r--r--spec/controllers/projects/templates_controller_spec.rb8
-rw-r--r--spec/controllers/projects/todo_controller_spec.rb8
-rw-r--r--spec/controllers/projects/tree_controller_spec.rb6
-rw-r--r--spec/controllers/projects/uploads_controller_spec.rb72
-rw-r--r--spec/controllers/projects/variables_controller_spec.rb8
-rw-r--r--spec/controllers/projects_controller_spec.rb72
-rw-r--r--spec/controllers/snippets_controller_spec.rb177
-rw-r--r--spec/controllers/uploads_controller_spec.rb22
-rw-r--r--spec/factories/ci/builds.rb25
-rw-r--r--spec/factories/commits.rb10
-rw-r--r--spec/factories/keys.rb7
-rw-r--r--spec/factories/notes.rb6
-rw-r--r--spec/factories/services.rb10
-rw-r--r--spec/factories/todos.rb12
-rw-r--r--spec/factories/users.rb13
-rw-r--r--spec/factories/wiki_directories.rb6
-rw-r--r--spec/factories/wiki_pages.rb18
-rw-r--r--spec/features/admin/admin_abuse_reports_spec.rb19
-rw-r--r--spec/features/admin/admin_labels_spec.rb11
-rw-r--r--spec/features/admin/admin_runners_spec.rb2
-rw-r--r--spec/features/atom/dashboard_issues_spec.rb2
-rw-r--r--spec/features/atom/issues_spec.rb4
-rw-r--r--spec/features/boards/add_issues_modal_spec.rb6
-rw-r--r--spec/features/boards/boards_spec.rb34
-rw-r--r--spec/features/boards/sidebar_spec.rb143
-rw-r--r--spec/features/calendar_spec.rb222
-rw-r--r--spec/features/commits_spec.rb4
-rw-r--r--spec/features/copy_as_gfm_spec.rb2
-rw-r--r--spec/features/dashboard/active_tab_spec.rb7
-rw-r--r--spec/features/dashboard/issuables_counter_spec.rb5
-rw-r--r--spec/features/dashboard/issues_spec.rb48
-rw-r--r--spec/features/dashboard/project_member_activity_index_spec.rb2
-rw-r--r--spec/features/dashboard/shortcuts_spec.rb10
-rw-r--r--spec/features/dashboard_issues_spec.rb4
-rw-r--r--spec/features/environment_spec.rb16
-rw-r--r--spec/features/expand_collapse_diffs_spec.rb28
-rw-r--r--spec/features/groups/members/list_spec.rb55
-rw-r--r--spec/features/groups_spec.rb19
-rw-r--r--spec/features/help_pages_spec.rb26
-rw-r--r--spec/features/issuables/issuable_list_spec.rb75
-rw-r--r--spec/features/issues/bulk_assignment_labels_spec.rb6
-rw-r--r--spec/features/issues/filtered_search/dropdown_label_spec.rb8
-rw-r--r--spec/features/issues/filtered_search/filter_issues_spec.rb69
-rw-r--r--spec/features/issues/gfm_autocomplete_spec.rb14
-rw-r--r--spec/features/issues/issue_sidebar_spec.rb65
-rw-r--r--spec/features/issues_spec.rb33
-rw-r--r--spec/features/login_spec.rb16
-rw-r--r--spec/features/merge_requests/closes_issues_spec.rb32
-rw-r--r--spec/features/merge_requests/conflicts_spec.rb4
-rw-r--r--spec/features/merge_requests/create_new_mr_spec.rb3
-rw-r--r--spec/features/merge_requests/filter_by_labels_spec.rb86
-rw-r--r--spec/features/merge_requests/filter_by_milestone_spec.rb35
-rw-r--r--spec/features/merge_requests/filter_merge_requests_spec.rb281
-rw-r--r--spec/features/merge_requests/mini_pipeline_graph_spec.rb2
-rw-r--r--spec/features/merge_requests/reset_filters_spec.rb29
-rw-r--r--spec/features/merge_requests/widget_spec.rb17
-rw-r--r--spec/features/milestones/milestones_spec.rb5
-rw-r--r--spec/features/profile_spec.rb4
-rw-r--r--spec/features/profiles/personal_access_tokens_spec.rb2
-rw-r--r--spec/features/profiles/preferences_spec.rb29
-rw-r--r--spec/features/profiles/user_changes_notified_of_own_activity_spec.rb32
-rw-r--r--spec/features/projects/badges/list_spec.rb6
-rw-r--r--spec/features/projects/builds_spec.rb6
-rw-r--r--spec/features/projects/files/find_file_keyboard_spec.rb4
-rw-r--r--spec/features/projects/files/project_owner_creates_license_file_spec.rb8
-rw-r--r--spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb4
-rw-r--r--spec/features/projects/import_export/export_file_spec.rb2
-rw-r--r--spec/features/projects/import_export/import_file_spec.rb2
-rw-r--r--spec/features/projects/import_export/namespace_export_file_spec.rb2
-rw-r--r--spec/features/projects/labels/issues_sorted_by_priority_spec.rb4
-rw-r--r--spec/features/projects/labels/update_prioritization_spec.rb3
-rw-r--r--spec/features/projects/main/download_buttons_spec.rb7
-rw-r--r--spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb16
-rw-r--r--spec/features/projects/members/sorting_spec.rb2
-rw-r--r--spec/features/projects/new_project_spec.rb45
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb12
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb103
-rw-r--r--spec/features/projects/ref_switcher_spec.rb8
-rw-r--r--spec/features/protected_branches/access_control_ce_spec.rb12
-rw-r--r--spec/features/search_spec.rb4
-rw-r--r--spec/features/snippets/user_snippets_spec.rb49
-rw-r--r--spec/features/todos/todos_filtering_spec.rb57
-rw-r--r--spec/features/todos/todos_spec.rb100
-rw-r--r--spec/features/variables_spec.rb28
-rw-r--r--spec/finders/group_members_finder_spec.rb32
-rw-r--r--spec/finders/issues_finder_spec.rb6
-rw-r--r--spec/finders/merge_requests_finder_spec.rb8
-rw-r--r--spec/finders/notes_finder_spec.rb2
-rw-r--r--spec/finders/pipelines_finder_spec.rb4
-rw-r--r--spec/fixtures/api/schemas/user/login.json1
-rw-r--r--spec/fixtures/api/schemas/user/public.json16
-rw-r--r--spec/fixtures/config/mail_room_disabled.yml (renamed from spec/fixtures/mail_room_disabled.yml)0
-rw-r--r--spec/fixtures/config/mail_room_enabled.yml (renamed from spec/fixtures/mail_room_enabled.yml)0
-rw-r--r--spec/helpers/application_helper_spec.rb9
-rw-r--r--spec/helpers/auth_helper_spec.rb2
-rw-r--r--spec/helpers/emails_helper_spec.rb32
-rw-r--r--spec/helpers/issuables_helper_spec.rb2
-rw-r--r--spec/helpers/issues_helper_spec.rb6
-rw-r--r--spec/helpers/merge_requests_helper_spec.rb82
-rw-r--r--spec/helpers/page_layout_helper_spec.rb12
-rw-r--r--spec/helpers/preferences_helper_spec.rb26
-rw-r--r--spec/helpers/submodule_helper_spec.rb44
-rw-r--r--spec/helpers/version_check_helper_spec.rb34
-rw-r--r--spec/helpers/wiki_helper_spec.rb21
-rw-r--r--spec/initializers/trusted_proxies_spec.rb4
-rw-r--r--spec/javascripts/.eslintrc3
-rw-r--r--spec/javascripts/ajax_loading_spinner_spec.js58
-rw-r--r--spec/javascripts/awards_handler_spec.js2
-rw-r--r--spec/javascripts/behaviors/autosize_spec.js2
-rw-r--r--spec/javascripts/behaviors/quick_submit_spec.js50
-rw-r--r--spec/javascripts/behaviors/requires_input_spec.js15
-rw-r--r--spec/javascripts/commit/pipelines/pipelines_store_spec.js.es67
-rw-r--r--spec/javascripts/dashboard_spec.js.es637
-rw-r--r--spec/javascripts/environments/environment_actions_spec.js.es66
-rw-r--r--spec/javascripts/environments/environment_external_url_spec.js.es64
-rw-r--r--spec/javascripts/environments/environment_item_spec.js.es638
-rw-r--r--spec/javascripts/environments/environment_rollback_spec.js.es68
-rw-r--r--spec/javascripts/environments/environment_spec.js.es697
-rw-r--r--spec/javascripts/environments/environment_stop_spec.js.es64
-rw-r--r--spec/javascripts/environments/environment_table_spec.js.es630
-rw-r--r--spec/javascripts/environments/environments_store_spec.js.es696
-rw-r--r--spec/javascripts/environments/folder/environments_folder_view_spec.js.es6202
-rw-r--r--spec/javascripts/environments/mock_data.js.es6211
-rw-r--r--spec/javascripts/extensions/array_spec.js.es62
-rw-r--r--spec/javascripts/extensions/jquery_spec.js2
-rw-r--r--spec/javascripts/filtered_search/dropdown_user_spec.js.es64
-rw-r--r--spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js.es623
-rw-r--r--spec/javascripts/fixtures/.gitignore1
-rw-r--r--spec/javascripts/fixtures/ajax_loading_spinner.html.haml2
-rw-r--r--spec/javascripts/fixtures/behaviors/quick_submit.html.haml6
-rw-r--r--spec/javascripts/fixtures/behaviors/requires_input.html.haml18
-rw-r--r--spec/javascripts/fixtures/branches.rb28
-rw-r--r--spec/javascripts/fixtures/builds.rb2
-rw-r--r--spec/javascripts/fixtures/dashboard.html.haml45
-rw-r--r--spec/javascripts/fixtures/emoji_menu.js2
-rw-r--r--spec/javascripts/fixtures/environments/environments_folder_view.html.haml7
-rw-r--r--spec/javascripts/fixtures/header.html.haml35
-rw-r--r--spec/javascripts/fixtures/issues.rb2
-rw-r--r--spec/javascripts/fixtures/merge_request_tabs.html.haml22
-rw-r--r--spec/javascripts/fixtures/merge_requests.rb36
-rw-r--r--spec/javascripts/fixtures/new_branch.html.haml4
-rw-r--r--spec/javascripts/fixtures/project_title.html.haml20
-rw-r--r--spec/javascripts/fixtures/projects.rb2
-rw-r--r--spec/javascripts/fixtures/todos.json4
-rw-r--r--spec/javascripts/fixtures/todos.rb52
-rw-r--r--spec/javascripts/gfm_auto_complete_spec.js.es657
-rw-r--r--spec/javascripts/gl_dropdown_spec.js.es68
-rw-r--r--spec/javascripts/header_spec.js8
-rw-r--r--spec/javascripts/helpers/class_spec_helper.js.es62
-rw-r--r--spec/javascripts/issue_spec.js6
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js.es624
-rw-r--r--spec/javascripts/lib/utils/text_utility_spec.js.es611
-rw-r--r--spec/javascripts/line_highlighter_spec.js2
-rw-r--r--spec/javascripts/merge_request_spec.js8
-rw-r--r--spec/javascripts/merge_request_tabs_spec.js46
-rw-r--r--spec/javascripts/merge_request_widget_spec.js2
-rw-r--r--spec/javascripts/new_branch_spec.js6
-rw-r--r--spec/javascripts/notes_spec.js14
-rw-r--r--spec/javascripts/project_dashboard_spec.js.es686
-rw-r--r--spec/javascripts/project_title_spec.js21
-rw-r--r--spec/javascripts/right_sidebar_spec.js6
-rw-r--r--spec/javascripts/search_autocomplete_spec.js6
-rw-r--r--spec/javascripts/shortcuts_issuable_spec.js2
-rw-r--r--spec/javascripts/syntax_highlight_spec.js2
-rw-r--r--spec/javascripts/test_bundle.js37
-rw-r--r--spec/javascripts/todos_spec.js63
-rw-r--r--spec/javascripts/u2f/authenticate_spec.js2
-rw-r--r--spec/javascripts/u2f/mock_u2f_device.js2
-rw-r--r--spec/javascripts/u2f/register_spec.js2
-rw-r--r--spec/javascripts/version_check_image_spec.js.es633
-rw-r--r--spec/javascripts/vue_shared/components/table_pagination_spec.js.es612
-rw-r--r--spec/javascripts/zen_mode_spec.js2
-rw-r--r--spec/lib/additional_email_headers_interceptor_spec.rb12
-rw-r--r--spec/lib/banzai/filter/issue_reference_filter_spec.rb24
-rw-r--r--spec/lib/banzai/filter/user_reference_filter_spec.rb13
-rw-r--r--spec/lib/bitbucket/collection_spec.rb2
-rw-r--r--spec/lib/bitbucket/representation/repo_spec.rb4
-rw-r--r--spec/lib/ci/gitlab_ci_yaml_processor_spec.rb44
-rw-r--r--spec/lib/constraints/project_url_constrainer_spec.rb4
-rw-r--r--spec/lib/expand_variables_spec.rb27
-rw-r--r--spec/lib/extracts_path_spec.rb4
-rw-r--r--spec/lib/gitlab/asciidoc_spec.rb23
-rw-r--r--spec/lib/gitlab/badge/shared/metadata.rb10
-rw-r--r--spec/lib/gitlab/bitbucket_import/importer_spec.rb8
-rw-r--r--spec/lib/gitlab/chat_commands/presenters/issue_show_spec.rb15
-rw-r--r--spec/lib/gitlab/ci/config/entry/commands_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/factory_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/config/entry/global_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/config/entry/key_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/paths_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/script_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/variables_spec.rb2
-rw-r--r--spec/lib/gitlab/conflict/file_spec.rb6
-rw-r--r--spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb45
-rw-r--r--spec/lib/gitlab/data_builder/build_spec.rb26
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb86
-rw-r--r--spec/lib/gitlab/database_spec.rb69
-rw-r--r--spec/lib/gitlab/diff/highlight_spec.rb6
-rw-r--r--spec/lib/gitlab/diff/position_tracer_spec.rb2
-rw-r--r--spec/lib/gitlab/git/blob_snippet_spec.rb2
-rw-r--r--spec/lib/gitlab/git/blob_spec.rb2
-rw-r--r--spec/lib/gitlab/git/diff_collection_spec.rb2
-rw-r--r--spec/lib/gitlab/git_access_spec.rb21
-rw-r--r--spec/lib/gitlab/git_spec.rb6
-rw-r--r--spec/lib/gitlab/github_import/comment_formatter_spec.rb18
-rw-r--r--spec/lib/gitlab/github_import/importer_spec.rb5
-rw-r--r--spec/lib/gitlab/github_import/issue_formatter_spec.rb27
-rw-r--r--spec/lib/gitlab/github_import/pull_request_formatter_spec.rb27
-rw-r--r--spec/lib/gitlab/github_import/user_formatter_spec.rb39
-rw-r--r--spec/lib/gitlab/import_export/attribute_configuration_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/avatar_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/file_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/import_export_spec.rb5
-rw-r--r--spec/lib/gitlab/import_export/model_configuration_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/project.light.json48
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb165
-rw-r--r--spec/lib/gitlab/import_export/project_tree_saver_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/repo_bundler_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/repo_restorer_spec.rb40
-rw-r--r--spec/lib/gitlab/import_export/wiki_repo_bundler_spec.rb2
-rw-r--r--spec/lib/gitlab/import_sources_spec.rb38
-rw-r--r--spec/lib/gitlab/kubernetes_spec.rb2
-rw-r--r--spec/lib/gitlab/ldap/auth_hash_spec.rb2
-rw-r--r--spec/lib/gitlab/ldap/user_spec.rb4
-rw-r--r--spec/lib/gitlab/metrics/instrumentation_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/method_call_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/metric_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/system_spec.rb6
-rw-r--r--spec/lib/gitlab/metrics/transaction_spec.rb4
-rw-r--r--spec/lib/gitlab/o_auth/user_spec.rb16
-rw-r--r--spec/lib/gitlab/optimistic_locking_spec.rb19
-rw-r--r--spec/lib/gitlab/other_markup.rb22
-rw-r--r--spec/lib/gitlab/reference_extractor_spec.rb75
-rw-r--r--spec/lib/gitlab/regex_spec.rb81
-rw-r--r--spec/lib/gitlab/saml/user_spec.rb21
-rw-r--r--spec/lib/gitlab/serializer/ci/variables_spec.rb (renamed from spec/lib/gitlab/serialize/ci/variables_spec.rb)5
-rw-r--r--spec/lib/gitlab/serializer/pagination_spec.rb49
-rw-r--r--spec/lib/gitlab/slash_commands/extractor_spec.rb8
-rw-r--r--spec/lib/gitlab/template/issue_template_spec.rb3
-rw-r--r--spec/lib/gitlab/template/merge_request_template_spec.rb3
-rw-r--r--spec/lib/gitlab/themes_spec.rb48
-rw-r--r--spec/lib/gitlab/upgrader_spec.rb3
-rw-r--r--spec/lib/gitlab/utils_spec.rb4
-rw-r--r--spec/lib/mattermost/command_spec.rb6
-rw-r--r--spec/lib/mattermost/team_spec.rb25
-rw-r--r--spec/migrations/migrate_process_commit_worker_jobs_spec.rb4
-rw-r--r--spec/models/abuse_report_spec.rb2
-rw-r--r--spec/models/application_setting_spec.rb40
-rw-r--r--spec/models/ci/build_spec.rb62
-rw-r--r--spec/models/ci/pipeline_spec.rb71
-rw-r--r--spec/models/ci/runner_spec.rb29
-rw-r--r--spec/models/commit_status_spec.rb22
-rw-r--r--spec/models/concerns/cache_markdown_field_spec.rb5
-rw-r--r--spec/models/concerns/routable_spec.rb34
-rw-r--r--spec/models/concerns/spammable_spec.rb19
-rw-r--r--spec/models/concerns/uniquify_spec.rb33
-rw-r--r--spec/models/cycle_analytics/staging_spec.rb2
-rw-r--r--spec/models/environment_spec.rb7
-rw-r--r--spec/models/member_spec.rb8
-rw-r--r--spec/models/members/project_member_spec.rb8
-rw-r--r--spec/models/merge_request_diff_spec.rb2
-rw-r--r--spec/models/merge_request_spec.rb50
-rw-r--r--spec/models/namespace_spec.rb53
-rw-r--r--spec/models/project_services/bamboo_service_spec.rb2
-rw-r--r--spec/models/project_services/buildkite_service_spec.rb4
-rw-r--r--spec/models/project_services/chat_message/build_message_spec.rb28
-rw-r--r--spec/models/project_services/drone_ci_service_spec.rb10
-rw-r--r--spec/models/project_services/irker_service_spec.rb4
-rw-r--r--spec/models/project_services/jira_service_spec.rb2
-rw-r--r--spec/models/project_services/kubernetes_service_spec.rb4
-rw-r--r--spec/models/project_services/mattermost_slash_commands_service_spec.rb3
-rw-r--r--spec/models/project_services/teamcity_service_spec.rb4
-rw-r--r--spec/models/project_spec.rb76
-rw-r--r--spec/models/repository_spec.rb17
-rw-r--r--spec/models/route_spec.rb45
-rw-r--r--spec/models/user_spec.rb102
-rw-r--r--spec/models/wiki_directory_spec.rb44
-rw-r--r--spec/models/wiki_page_spec.rb118
-rw-r--r--spec/policies/user_policy_spec.rb37
-rw-r--r--spec/requests/api/access_requests_spec.rb1
-rw-r--r--spec/requests/api/award_emoji_spec.rb1
-rw-r--r--spec/requests/api/boards_spec.rb2
-rw-r--r--spec/requests/api/branches_spec.rb39
-rw-r--r--spec/requests/api/broadcast_messages_spec.rb1
-rw-r--r--spec/requests/api/builds_spec.rb16
-rw-r--r--spec/requests/api/commit_statuses_spec.rb18
-rw-r--r--spec/requests/api/commits_spec.rb59
-rw-r--r--spec/requests/api/deploy_keys_spec.rb23
-rw-r--r--spec/requests/api/deployments_spec.rb5
-rw-r--r--spec/requests/api/environments_spec.rb5
-rw-r--r--spec/requests/api/files_spec.rb8
-rw-r--r--spec/requests/api/fork_spec.rb134
-rw-r--r--spec/requests/api/groups_spec.rb81
-rw-r--r--spec/requests/api/issues_spec.rb200
-rw-r--r--spec/requests/api/labels_spec.rb35
-rw-r--r--spec/requests/api/members_spec.rb11
-rw-r--r--spec/requests/api/merge_request_diffs_spec.rb2
-rw-r--r--spec/requests/api/merge_requests_spec.rb52
-rw-r--r--spec/requests/api/milestones_spec.rb75
-rw-r--r--spec/requests/api/namespaces_spec.rb18
-rw-r--r--spec/requests/api/notes_spec.rb9
-rw-r--r--spec/requests/api/pipelines_spec.rb5
-rw-r--r--spec/requests/api/project_hooks_spec.rb1
-rw-r--r--spec/requests/api/project_snippets_spec.rb99
-rw-r--r--spec/requests/api/projects_spec.rb534
-rw-r--r--spec/requests/api/repositories_spec.rb7
-rw-r--r--spec/requests/api/runners_spec.rb22
-rw-r--r--spec/requests/api/settings_spec.rb11
-rw-r--r--spec/requests/api/snippets_spec.rb73
-rw-r--r--spec/requests/api/system_hooks_spec.rb1
-rw-r--r--spec/requests/api/tags_spec.rb10
-rw-r--r--spec/requests/api/templates_spec.rb70
-rw-r--r--spec/requests/api/todos_spec.rb33
-rw-r--r--spec/requests/api/triggers_spec.rb1
-rw-r--r--spec/requests/api/users_spec.rb70
-rw-r--r--spec/requests/api/v3/boards_spec.rb79
-rw-r--r--spec/requests/api/v3/branches_spec.rb44
-rw-r--r--spec/requests/api/v3/commits_spec.rb578
-rw-r--r--spec/requests/api/v3/files_spec.rb270
-rw-r--r--spec/requests/api/v3/groups_spec.rb35
-rw-r--r--spec/requests/api/v3/issues_spec.rb29
-rw-r--r--spec/requests/api/v3/labels_spec.rb152
-rw-r--r--spec/requests/api/v3/members_spec.rb342
-rw-r--r--spec/requests/api/v3/merge_requests_spec.rb2
-rw-r--r--spec/requests/api/v3/notes_spec.rb432
-rw-r--r--spec/requests/api/v3/project_snippets_spec.rb92
-rw-r--r--spec/requests/api/v3/projects_spec.rb3
-rw-r--r--spec/requests/api/v3/repositories_spec.rb144
-rw-r--r--spec/requests/api/v3/system_hooks_spec.rb41
-rw-r--r--spec/requests/api/v3/tags_spec.rb67
-rw-r--r--spec/requests/api/v3/templates_spec.rb203
-rw-r--r--spec/requests/api/v3/todos_spec.rb73
-rw-r--r--spec/requests/api/v3/users_spec.rb266
-rw-r--r--spec/requests/ci/api/builds_spec.rb35
-rw-r--r--spec/requests/ci/api/runners_spec.rb2
-rw-r--r--spec/requests/ci/api/triggers_spec.rb3
-rw-r--r--spec/requests/lfs_http_spec.rb177
-rw-r--r--spec/rubocop/cop/gem_fetcher_spec.rb46
-rw-r--r--spec/rubocop/cop/migration/add_column_with_default_spec.rb41
-rw-r--r--spec/rubocop/cop/migration/add_concurrent_foreign_key_spec.rb33
-rw-r--r--spec/serializers/environment_serializer_spec.rb143
-rw-r--r--spec/serializers/pipeline_serializer_spec.rb6
-rw-r--r--spec/services/auth/container_registry_authentication_service_spec.rb2
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb71
-rw-r--r--spec/services/ci/process_pipeline_service_spec.rb18
-rw-r--r--spec/services/ci/retry_build_service_spec.rb117
-rw-r--r--spec/services/ci/retry_pipeline_service_spec.rb196
-rw-r--r--spec/services/ci/update_runner_service_spec.rb41
-rw-r--r--spec/services/create_deployment_service_spec.rb17
-rw-r--r--spec/services/delete_user_service_spec.rb60
-rw-r--r--spec/services/groups/destroy_service_spec.rb (renamed from spec/services/destroy_group_service_spec.rb)35
-rw-r--r--spec/services/issues/build_service_spec.rb11
-rw-r--r--spec/services/issues/create_service_spec.rb11
-rw-r--r--spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb25
-rw-r--r--spec/services/merge_requests/build_service_spec.rb13
-rw-r--r--spec/services/merge_requests/merge_service_spec.rb29
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb41
-rw-r--r--spec/services/merge_requests/resolve_service_spec.rb8
-rw-r--r--spec/services/notes/destroy_service_spec.rb (renamed from spec/services/notes/delete_service_spec.rb)2
-rw-r--r--spec/services/notification_service_spec.rb59
-rw-r--r--spec/services/projects/create_service_spec.rb4
-rw-r--r--spec/services/projects/destroy_service_spec.rb19
-rw-r--r--spec/services/protected_branches/create_service_spec.rb4
-rw-r--r--spec/services/spam_service_spec.rb71
-rw-r--r--spec/services/system_note_service_spec.rb2
-rw-r--r--spec/services/tags/create_service_spec.rb (renamed from spec/services/create_tag_service_spec.rb)2
-rw-r--r--spec/services/tags/destroy_service_spec.rb (renamed from spec/services/delete_tag_service_spec.rb)2
-rw-r--r--spec/services/todo_service_spec.rb242
-rw-r--r--spec/services/users/destroy_spec.rb130
-rw-r--r--spec/services/wiki_pages/create_service_spec.rb36
-rw-r--r--spec/services/wiki_pages/destroy_service_spec.rb21
-rw-r--r--spec/services/wiki_pages/update_service_spec.rb37
-rw-r--r--spec/support/api/pagination_shared_examples.rb20
-rw-r--r--spec/support/db_cleaner.rb4
-rw-r--r--spec/support/drag_to_helper.rb13
-rw-r--r--spec/support/filtered_search_helpers.rb37
-rw-r--r--spec/support/gitlab_stubs/session.json4
-rw-r--r--spec/support/gitlab_stubs/user.json4
-rw-r--r--spec/support/issuables_list_metadata_shared_examples.rb36
-rw-r--r--spec/support/javascript_fixtures_helpers.rb2
-rw-r--r--spec/support/jira_service_helper.rb2
-rw-r--r--spec/support/kubernetes_helpers.rb13
-rw-r--r--spec/support/login_helpers.rb11
-rw-r--r--spec/support/markdown_feature.rb4
-rw-r--r--spec/support/matchers/access_matchers.rb2
-rw-r--r--spec/support/matchers/pagination_matcher.rb5
-rw-r--r--spec/support/matchers/satisfy_matchers.rb19
-rw-r--r--spec/support/merge_request_helpers.rb9
-rw-r--r--spec/support/repo_helpers.rb16
-rw-r--r--spec/support/seed_helper.rb2
-rw-r--r--spec/support/seed_repo.rb76
-rw-r--r--spec/support/select2_helper.rb2
-rw-r--r--spec/support/services/issuable_create_service_slash_commands_shared_examples.rb4
-rw-r--r--spec/support/slash_commands_helpers.rb2
-rw-r--r--spec/support/stub_gitlab_calls.rb2
-rw-r--r--spec/support/test_env.rb8
-rw-r--r--spec/support/time_tracking_shared_examples.rb2
-rw-r--r--spec/views/projects/_home_panel.html.haml_spec.rb38
-rw-r--r--spec/views/projects/notes/_form.html.haml_spec.rb4
-rw-r--r--spec/workers/delete_user_worker_spec.rb4
-rw-r--r--spec/workers/git_garbage_collect_worker_spec.rb4
-rw-r--r--spec/workers/repository_fork_worker_spec.rb20
-rw-r--r--spec/workers/repository_import_worker_spec.rb2
-rw-r--r--vendor/assets/javascripts/es6-promise.auto.js1156
-rw-r--r--vendor/assets/javascripts/timeago.js237
-rw-r--r--vendor/assets/javascripts/vue-resource.full.js1318
-rw-r--r--vendor/assets/javascripts/vue-resource.js.erb2
-rw-r--r--vendor/assets/javascripts/vue-resource.min.js7
-rw-r--r--vendor/assets/javascripts/vue.full.js7515
-rw-r--r--vendor/assets/javascripts/vue.js.erb2
-rw-r--r--vendor/assets/javascripts/vue.min.js7
-rw-r--r--yarn.lock4632
1793 files changed, 29457 insertions, 22955 deletions
diff --git a/.eslintrc b/.eslintrc
index 1a2cd821af7..0fcd866778f 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -12,12 +12,18 @@
"localStorage": false
},
"plugins": [
- "filenames"
+ "filenames",
+ "import"
],
+ "settings": {
+ "import/resolver": {
+ "webpack": {
+ "config": "./config/webpack.config.js"
+ }
+ }
+ },
"rules": {
"filenames/match-regex": [2, "^[a-z0-9_]+(.js)?$"],
- "no-multiple-empty-lines": ["error", { "max": 1 }],
- "import/no-extraneous-dependencies": "off",
- "import/no-unresolved": "off"
+ "no-multiple-empty-lines": ["error", { "max": 1 }]
}
}
diff --git a/.gitignore b/.gitignore
index 0b602d613c7..680651986e8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -51,3 +51,4 @@ eslint-report.html
/builds/*
/shared/*
/.gitlab_workhorse_secret
+/webpack-report/
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 5ab3648d9f2..e7a279c828b 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -107,7 +107,10 @@ setup-test-env:
<<: *dedicated-runner
stage: prepare
script:
- - npm install
+ - node --version
+ - yarn --version
+ - yarn install --pure-lockfile
+ - yarn check # ensure that yarn.lock matches package.json
- bundle exec rake gitlab:assets:compile
- bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init'
artifacts:
@@ -163,64 +166,7 @@ spinach 7 10: *spinach-knapsack
spinach 8 10: *spinach-knapsack
spinach 9 10: *spinach-knapsack
-# Execute all testing suites against Ruby 2.1
-.ruby-21: &ruby-21
- image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.1-git-2.7-phantomjs-2.1"
- <<: *use-db
- only:
- - master@gitlab-org/gitlab-ce
- - master@gitlab-org/gitlab-ee
- - master@gitlab/gitlabhq
- - master@gitlab/gitlab-ee
- cache:
- key: "ruby21"
- paths:
- - vendor/ruby
-
-.rspec-knapsack-ruby21: &rspec-knapsack-ruby21
- <<: *rspec-knapsack
- <<: *dedicated-runner
- <<: *ruby-21
-
-.spinach-knapsack-ruby21: &spinach-knapsack-ruby21
- <<: *spinach-knapsack
- <<: *dedicated-runner
- <<: *ruby-21
-
-rspec 0 20 ruby21: *rspec-knapsack-ruby21
-rspec 1 20 ruby21: *rspec-knapsack-ruby21
-rspec 2 20 ruby21: *rspec-knapsack-ruby21
-rspec 3 20 ruby21: *rspec-knapsack-ruby21
-rspec 4 20 ruby21: *rspec-knapsack-ruby21
-rspec 5 20 ruby21: *rspec-knapsack-ruby21
-rspec 6 20 ruby21: *rspec-knapsack-ruby21
-rspec 7 20 ruby21: *rspec-knapsack-ruby21
-rspec 8 20 ruby21: *rspec-knapsack-ruby21
-rspec 9 20 ruby21: *rspec-knapsack-ruby21
-rspec 10 20 ruby21: *rspec-knapsack-ruby21
-rspec 11 20 ruby21: *rspec-knapsack-ruby21
-rspec 12 20 ruby21: *rspec-knapsack-ruby21
-rspec 13 20 ruby21: *rspec-knapsack-ruby21
-rspec 14 20 ruby21: *rspec-knapsack-ruby21
-rspec 15 20 ruby21: *rspec-knapsack-ruby21
-rspec 16 20 ruby21: *rspec-knapsack-ruby21
-rspec 17 20 ruby21: *rspec-knapsack-ruby21
-rspec 18 20 ruby21: *rspec-knapsack-ruby21
-rspec 19 20 ruby21: *rspec-knapsack-ruby21
-
-spinach 0 10 ruby21: *spinach-knapsack-ruby21
-spinach 1 10 ruby21: *spinach-knapsack-ruby21
-spinach 2 10 ruby21: *spinach-knapsack-ruby21
-spinach 3 10 ruby21: *spinach-knapsack-ruby21
-spinach 4 10 ruby21: *spinach-knapsack-ruby21
-spinach 5 10 ruby21: *spinach-knapsack-ruby21
-spinach 6 10 ruby21: *spinach-knapsack-ruby21
-spinach 7 10 ruby21: *spinach-knapsack-ruby21
-spinach 8 10 ruby21: *spinach-knapsack-ruby21
-spinach 9 10 ruby21: *spinach-knapsack-ruby21
-
# Other generic tests
-
.ruby-static-analysis: &ruby-static-analysis
variables:
SIMPLECOV: "false"
@@ -294,7 +240,26 @@ rake db:seed_fu:
paths:
- log/development.log
-karma:
+rake gitlab:assets:compile:
+ stage: test
+ <<: *dedicated-runner
+ dependencies: []
+ variables:
+ NODE_ENV: "production"
+ RAILS_ENV: "production"
+ SETUP_DB: "false"
+ USE_DB: "false"
+ SKIP_STORAGE_VALIDATION: "true"
+ WEBPACK_REPORT: "true"
+ script:
+ - bundle exec rake yarn:install gitlab:assets:compile
+ artifacts:
+ name: webpack-report
+ expire_in: 31d
+ paths:
+ - webpack-report/
+
+rake karma:
cache:
paths:
- vendor/ruby
@@ -303,13 +268,12 @@ karma:
<<: *use-db
<<: *dedicated-runner
script:
- - npm link istanbul
- bundle exec rake karma
artifacts:
name: coverage-javascript
expire_in: 31d
paths:
- - coverage-javascript/default/
+ - coverage-javascript/
lint-doc:
stage: test
@@ -336,7 +300,7 @@ bundler:audit:
- master@gitlab/gitlabhq
- master@gitlab/gitlab-ee
script:
- - "bundle exec bundle-audit check --update --ignore OSVDB-115941"
+ - "bundle exec bundle-audit check --update --ignore OSVDB-115941 CVE-2016-6316 CVE-2016-6317"
migration paths:
stage: test
@@ -382,11 +346,9 @@ lint:javascript:
paths:
- node_modules/
stage: test
- image: "node:7.1"
- before_script:
- - npm install
+ before_script: []
script:
- - npm --silent run eslint
+ - yarn run eslint
lint:javascript:report:
<<: *dedicated-runner
@@ -394,12 +356,10 @@ lint:javascript:report:
paths:
- node_modules/
stage: post-test
- image: "node:7.1"
- before_script:
- - npm install
+ before_script: []
script:
- find app/ spec/ -name '*.js' -or -name '*.js.es6' -exec sed --in-place 's|/\* eslint-disable .*\*/||' {} \; # run report over all files
- - npm --silent run eslint-report || true # ignore exit code
+ - yarn run eslint-report || true # ignore exit code
artifacts:
name: eslint-report
expire_in: 31d
@@ -446,14 +406,16 @@ pages:
<<: *dedicated-runner
dependencies:
- coverage
- - karma
+ - rake karma
+ - rake gitlab:assets:compile
- lint:javascript:report
script:
- mv public/ .public/
- mkdir public/
- mv coverage/ public/coverage-ruby/ || true
- - mv coverage-javascript/default/ public/coverage-javascript/ || true
+ - mv coverage-javascript/ public/coverage-javascript/ || true
- mv eslint-report.html public/ || true
+ - mv webpack-report/ public/webpack-report/ || true
artifacts:
paths:
- public
diff --git a/.gitlab/issue_templates/Bug.md b/.gitlab/issue_templates/Bug.md
index 6d7d88c6791..34c2e097ba8 100644
--- a/.gitlab/issue_templates/Bug.md
+++ b/.gitlab/issue_templates/Bug.md
@@ -6,13 +6,13 @@
(How one can reproduce the issue - this is very important)
-### Expected behavior
+### What is the current *bug* behavior?
-(What you should see instead)
+(What actually happens)
-### Actual behavior
+### What is the expected *correct* behavior?
-(What actually happens)
+(What you should see instead)
### Relevant logs and/or screenshots
@@ -23,23 +23,23 @@ logs, and code as it's very hard to read otherwise.)
(If you are reporting a bug on GitLab.com, write: This bug happens on GitLab.com)
-#### Results of GitLab application Check
+#### Results of GitLab environment info
(For installations with omnibus-gitlab package run and paste the output of:
-`sudo gitlab-rake gitlab:check SANITIZE=true`)
+`sudo gitlab-rake gitlab:env:info`)
(For installations from source run and paste the output of:
-`sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true`)
-
-(we will only investigate if the tests are passing)
+`sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`)
-#### Results of GitLab environment info
+#### Results of GitLab application Check
(For installations with omnibus-gitlab package run and paste the output of:
-`sudo gitlab-rake gitlab:env:info`)
+`sudo gitlab-rake gitlab:check SANITIZE=true`)
(For installations from source run and paste the output of:
-`sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`)
+`sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true`)
+
+(we will only investigate if the tests are passing)
### Possible fixes
diff --git a/.gitlab/issue_templates/Research Proposal.md b/.gitlab/issue_templates/Research Proposal.md
new file mode 100644
index 00000000000..5676656793d
--- /dev/null
+++ b/.gitlab/issue_templates/Research Proposal.md
@@ -0,0 +1,17 @@
+### Background:
+
+(Include problem, use cases, benefits, and/or goals)
+
+**What questions are you trying to answer?**
+
+**Are you looking to verify an existing hypothesis or uncover new issues you should be exploring?**
+
+**What is the backstory of this project and how does it impact the approach?**
+
+**What do you already know about the areas you are exploring?**
+
+**What does success look like at the end of the project?**
+
+### Links / references:
+
+/label ~"UX research"
diff --git a/.rubocop.yml b/.rubocop.yml
index cfff42e5c99..38b71d74fea 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -5,7 +5,7 @@ require:
inherit_from: .rubocop_todo.yml
AllCops:
- TargetRubyVersion: 2.1
+ TargetRubyVersion: 2.3
# Cop names are not d§splayed in offense messages by default. Change behavior
# by overriding DisplayCopNames, or by giving the -D/--display-cop-names
# option.
@@ -22,17 +22,13 @@ AllCops:
- 'db/fixtures/**/*'
- 'tmp/**/*'
- 'bin/**/*'
- - 'lib/backup/**/*'
- - 'lib/ci/backup/**/*'
- - 'lib/tasks/**/*'
- - 'lib/ci/migrate/**/*'
- - 'lib/email_validator.rb'
- - 'lib/gitlab/upgrader.rb'
- - 'lib/gitlab/seeder.rb'
- 'generator_templates/**/*'
+# Gems in consecutive lines should be alphabetically sorted
+Bundler/OrderedGems:
+ Enabled: false
-##################### Style ##################################
+# Style #######################################################################
# Check indentation of private/protected visibility modifiers.
Style/AccessModifierIndentation:
@@ -55,6 +51,16 @@ Style/AlignArray:
Style/AlignHash:
Enabled: true
+# Here we check if the parameters on a multi-line method call or
+# definition are aligned.
+Style/AlignParameters:
+ Enabled: false
+
+# Whether `and` and `or` are banned only in conditionals (conditionals)
+# or completely (always).
+Style/AndOr:
+ Enabled: true
+
# Use `Array#join` instead of `Array#*`.
Style/ArrayJoin:
Enabled: true
@@ -79,15 +85,24 @@ Style/BeginBlock:
Style/BlockComments:
Enabled: true
-# Put end statement of multiline block on its own line.
-Style/BlockEndNewline:
- Enabled: true
-
# Avoid using {...} for multi-line blocks (multiline chaining is # always
# ugly). Prefer {...} over do...end for single-line blocks.
Style/BlockDelimiters:
Enabled: true
+# Put end statement of multiline block on its own line.
+Style/BlockEndNewline:
+ Enabled: true
+
+ # This cop checks for braces around the last parameter in a method call
+# if the last parameter is a hash.
+Style/BracesAroundHashParameters:
+ Enabled: false
+
+# This cop checks for uses of the case equality operator(===).
+Style/CaseEquality:
+ Enabled: false
+
# Indentation of when in a case/when/[else/]end.
Style/CaseIndentation:
Enabled: true
@@ -106,7 +121,7 @@ Style/ClassAndModuleChildren:
# Enforces consistent use of `Object#is_a?` or `Object#kind_of?`.
Style/ClassCheck:
- Enabled: false
+ Enabled: true
# Use self when defining module/class methods.
Style/ClassMethods:
@@ -116,10 +131,26 @@ Style/ClassMethods:
Style/ClassVars:
Enabled: true
+# This cop checks for methods invoked via the :: operator instead
+# of the . operator (like FileUtils::rmdir instead of FileUtils.rmdir).
+Style/ColonMethodCall:
+ Enabled: true
+
+# This cop checks that comment annotation keywords are written according
+# to guidelines.
+Style/CommentAnnotation:
+ Enabled: false
+
# Indentation of comments.
Style/CommentIndentation:
Enabled: true
+# Check for `if` and `case` statements where each branch is used for
+# assignment to the same variable when using the return of the
+# condition can be used instead.
+Style/ConditionalAssignment:
+ Enabled: true
+
# Constants should use SCREAMING_SNAKE_CASE.
Style/ConstantName:
Enabled: true
@@ -132,13 +163,19 @@ Style/DefWithParentheses:
Style/Documentation:
Enabled: false
+# This cop checks for uses of double negation (!!) to convert something
+# to a boolean value. As this is both cryptic and usually redundant, it
+# should be avoided.
+Style/DoubleNegation:
+ Enabled: false
+
# Align elses and elsifs correctly.
Style/ElseAlignment:
Enabled: true
# Use empty lines between defs.
Style/EmptyLineBetweenDefs:
- Enabled: false
+ Enabled: true
# Don't use several empty lines in a row.
Style/EmptyLines:
@@ -156,14 +193,14 @@ Style/EmptyLinesAroundBlockBody:
Style/EmptyLinesAroundClassBody:
Enabled: true
-# Keeps track of empty lines around module bodies.
-Style/EmptyLinesAroundModuleBody:
- Enabled: true
-
# Keeps track of empty lines around method bodies.
Style/EmptyLinesAroundMethodBody:
Enabled: true
+# Keeps track of empty lines around module bodies.
+Style/EmptyLinesAroundModuleBody:
+ Enabled: true
+
# Avoid the use of END blocks.
Style/EndBlock:
Enabled: true
@@ -196,24 +233,28 @@ Style/For:
# Checks if there is a magic comment to enforce string literals
Style/FrozenStringLiteralComment:
Enabled: false
+
# Do not introduce global variables.
Style/GlobalVars:
Enabled: true
+ Exclude:
+ - 'lib/backup/**/*'
+ - 'lib/tasks/**/*'
# Prefer Ruby 1.9 hash syntax `{ a: 1, b: 2 }`
# over 1.8 syntax `{ :a => 1, :b => 2 }`.
Style/HashSyntax:
Enabled: true
-# Do not use if x; .... Use the ternary operator instead.
-Style/IfWithSemicolon:
- Enabled: true
-
# Checks that conditional statements do not have an identical line at the
# end of each branch, which can validly be moved out of the conditional.
Style/IdenticalConditionalBranches:
Enabled: true
+# Do not use if x; .... Use the ternary operator instead.
+Style/IfWithSemicolon:
+ Enabled: true
+
# Checks the indentation of the first line of the right-hand-side of a
# multi-line assignment.
Style/IndentAssignment:
@@ -254,7 +295,7 @@ Style/ModuleFunction:
# Checks that the closing brace in an array literal is either on the same line
# as the last array element, or a new line.
Style/MultilineArrayBraceLayout:
- Enabled: false
+ Enabled: true
EnforcedStyle: symmetrical
# Avoid multi-line chains of blocks.
@@ -268,7 +309,7 @@ Style/MultilineBlockLayout:
# Checks that the closing brace in a hash literal is either on the same line as
# the last hash element, or a new line.
Style/MultilineHashBraceLayout:
- Enabled: false
+ Enabled: true
EnforcedStyle: symmetrical
# Do not use then for multi-line if/unless.
@@ -300,6 +341,14 @@ Style/MultilineOperationIndentation:
Style/MultilineTernaryOperator:
Enabled: true
+# This cop checks whether some constant value isn't a
+# mutable literal (e.g. array or hash).
+Style/MutableConstant:
+ Enabled: true
+ Exclude:
+ - 'db/migrate/**/*'
+ - 'db/post_migrate/**/*'
+
# Favor unless over if for negative conditions (or control flow or).
Style/NegatedIf:
Enabled: true
@@ -340,6 +389,10 @@ Style/OpMethod:
Style/ParenthesesAroundCondition:
Enabled: true
+# Checks for an obsolete RuntimeException argument in raise/fail.
+Style/RedundantException:
+ Enabled: true
+
# Checks for parentheses that seem not to serve any purpose.
Style/RedundantParentheses:
Enabled: true
@@ -398,6 +451,10 @@ Style/SpaceBeforeComment:
Style/SpaceBeforeSemicolon:
Enabled: true
+# Checks for spaces inside square brackets.
+Style/SpaceInsideBrackets:
+ Enabled: true
+
# Use spaces inside hash literal braces - or don't.
Style/SpaceInsideHashLiteralBraces:
Enabled: true
@@ -434,6 +491,10 @@ Style/Tab:
Style/TrailingBlankLines:
Enabled: true
+# This cop checks for trailing comma in array and hash literals.
+Style/TrailingCommaInLiteral:
+ Enabled: false
+
# Checks for %W when interpolation is not needed.
Style/UnneededCapitalW:
Enabled: true
@@ -469,9 +530,9 @@ Style/WhileUntilModifier:
# Use %w or %W for arrays of words.
Style/WordArray:
- Enabled: false
+ Enabled: true
-#################### Metrics ################################
+# Metrics #####################################################################
# A calculated magnitude based on number of assignments,
# branches, and conditions.
@@ -479,6 +540,10 @@ Metrics/AbcSize:
Enabled: true
Max: 60
+# This cop checks if the length of a block exceeds some maximum value.
+Metrics/BlockLength:
+ Enabled: false
+
# Avoid excessive block nesting.
Metrics/BlockNesting:
Enabled: true
@@ -516,23 +581,23 @@ Metrics/PerceivedComplexity:
Enabled: true
Max: 18
-
-#################### Lint ################################
-
-# Checks for useless access modifiers.
-Lint/UselessAccessModifier:
- Enabled: true
-
-# Checks for attempts to use `private` or `protected` to set the visibility
-# of a class method, which does not work.
-Lint/IneffectiveAccessModifier:
- Enabled: false
+# Lint ########################################################################
# Checks for ambiguous operators in the first argument of a method invocation
# without parentheses.
Lint/AmbiguousOperator:
Enabled: true
+# This cop checks for ambiguous regexp literals in the first argument of
+# a method invocation without parentheses.
+Lint/AmbiguousRegexpLiteral:
+ Enabled: false
+
+# This cop checks for assignments in the conditions of
+# if/while/until.
+Lint/AssignmentInCondition:
+ Enabled: false
+
# Align block ends correctly.
Lint/BlockAlignment:
Enabled: true
@@ -570,6 +635,10 @@ Lint/ElseLayout:
Lint/EmptyEnsure:
Enabled: true
+# Checks for the presence of `when` branches without a body.
+Lint/EmptyWhen:
+ Enabled: true
+
# Align ends correctly.
Lint/EndAlignment:
Enabled: true
@@ -582,10 +651,6 @@ Lint/EndInMethod:
Lint/EnsureReturn:
Enabled: true
-# The use of eval represents a serious security risk.
-Lint/Eval:
- Enabled: true
-
# Catches floating-point literals too large or small for Ruby to represent.
Lint/FloatOutOfRange:
Enabled: true
@@ -594,11 +659,20 @@ Lint/FloatOutOfRange:
Lint/FormatParameterMismatch:
Enabled: true
+# This cop checks for *rescue* blocks with no body.
+Lint/HandleExceptions:
+ Enabled: false
+
# Checks for adjacent string literals on the same line, which could better be
# represented as a single string literal.
Lint/ImplicitStringConcatenation:
Enabled: true
+# Checks for attempts to use `private` or `protected` to set the visibility
+# of a class method, which does not work.
+Lint/IneffectiveAccessModifier:
+ Enabled: false
+
# Checks for invalid character literals with a non-escaped whitespace
# character.
Lint/InvalidCharacterLiteral:
@@ -612,6 +686,10 @@ Lint/LiteralInCondition:
Lint/LiteralInInterpolation:
Enabled: true
+# This cop checks for uses of *begin...end while/until something*.
+Lint/Loop:
+ Enabled: false
+
# Do not use nested method definitions.
Lint/NestedMethodDefinition:
Enabled: true
@@ -641,6 +719,11 @@ Lint/RescueException:
Lint/ShadowedException:
Enabled: false
+# This cop looks for use of the same name as outer local variables
+# for block arguments or block local variables.
+Lint/ShadowingOuterLocalVariable:
+ Enabled: false
+
# Checks for Object#to_s usage in string interpolation.
Lint/StringConversionInInterpolation:
Enabled: true
@@ -649,16 +732,36 @@ Lint/StringConversionInInterpolation:
Lint/UnderscorePrefixedVariableName:
Enabled: true
+# This cop checks for using Fixnum or Bignum constant
+Lint/UnifiedInteger:
+ Enabled: true
+
# Checks for rubocop:disable comments that can be removed.
# Note: this cop is not disabled when disabling all cops.
# It must be explicitly disabled.
Lint/UnneededDisable:
Enabled: false
+# This cop checks for unneeded usages of splat expansion
+Lint/UnneededSplatExpansion:
+ Enabled: false
+
# Unreachable code.
Lint/UnreachableCode:
Enabled: true
+# This cop checks for unused block arguments.
+Lint/UnusedBlockArgument:
+ Enabled: false
+
+# This cop checks for unused method arguments.
+Lint/UnusedMethodArgument:
+ Enabled: false
+
+# Checks for useless access modifiers.
+Lint/UselessAccessModifier:
+ Enabled: true
+
# Checks for useless assignment to a local variable.
Lint/UselessAssignment:
Enabled: true
@@ -679,8 +782,7 @@ Lint/UselessSetterCall:
Lint/Void:
Enabled: true
-
-##################### Performance ############################
+# Performance #################################################################
# Use `casecmp` rather than `downcase ==`.
Performance/Casecmp:
@@ -699,6 +801,22 @@ Performance/LstripRstrip:
Performance/RangeInclude:
Enabled: true
+# This cop identifies the use of a `&block` parameter and `block.call`
+# where `yield` would do just as well.
+Performance/RedundantBlockCall:
+ Enabled: true
+
+# This cop identifies use of `Regexp#match` or `String#match in a context
+# where the integral return value of `=~` would do just as well.
+Performance/RedundantMatch:
+ Enabled: true
+
+# This cop identifies places where `Hash#merge!` can be replaced by
+# `Hash#[]=`.
+Performance/RedundantMerge:
+ Enabled: true
+ MaxKeyValuePairs: 1
+
# Use `sort` instead of `sort_by { |x| x }`.
Performance/RedundantSortBy:
Enabled: true
@@ -718,8 +836,18 @@ Performance/StringReplacement:
Performance/TimesMap:
Enabled: true
+# Security ####################################################################
+
+# This cop checks for the use of JSON class methods which have potential
+# security issues.
+Security/JSONLoad:
+ Enabled: true
+
+# This cop checks for the use of *Kernel#eval*.
+Security/Eval:
+ Enabled: true
-##################### Rails ##################################
+# Rails #######################################################################
# Enables Rails cops.
Rails:
@@ -737,8 +865,19 @@ Rails/Date:
# Prefer delegate method for delegations.
Rails/Delegate:
+ Enabled: true
+
+# This cop checks dynamic `find_by_*` methods.
+Rails/DynamicFindBy:
Enabled: false
+# This cop enforces that 'exit' calls are not used within a rails app.
+Rails/Exit:
+ Enabled: true
+ Exclude:
+ - lib/gitlab/upgrader.rb
+ - 'lib/backup/**/*'
+
# Prefer `find_by` over `where.first`.
Rails/FindBy:
Enabled: true
@@ -751,9 +890,25 @@ Rails/FindEach:
Rails/HasAndBelongsToMany:
Enabled: true
+# This cop is used to identify usages of http methods like `get`, `post`,
+# `put`, `patch` without the usage of keyword arguments in your tests and
+# change them to use keyword args.
+Rails/HttpPositionalArguments:
+ Enabled: false
+
# Checks for calls to puts, print, etc.
Rails/Output:
Enabled: true
+ Exclude:
+ - lib/gitlab/seeder.rb
+ - lib/gitlab/upgrader.rb
+ - 'lib/backup/**/*'
+ - 'lib/tasks/**/*'
+
+# This cop checks for the use of output safety calls like html_safe and
+# raw.
+Rails/OutputSafety:
+ Enabled: false
# Checks for incorrect grammar when using methods like `3.day.ago`.
Rails/PluralizationGrammar:
@@ -767,12 +922,24 @@ Rails/ReadWriteAttribute:
Rails/ScopeArgs:
Enabled: true
-##################### RSpec ##################################
+# This cop checks for the use of Time methods without zone.
+Rails/TimeZone:
+ Enabled: false
+
+# This cop checks for the use of old-style attribute validation macros.
+Rails/Validation:
+ Enabled: true
+
+# RSpec #######################################################################
# Check that instances are not being stubbed globally.
RSpec/AnyInstance:
Enabled: false
+# Check for expectations where `be(...)` can replace `eql(...)`.
+RSpec/BeEql:
+ Enabled: true
+
# Check that the first argument to the top level describe is the tested class or
# module.
RSpec/DescribeClass:
@@ -801,6 +968,10 @@ RSpec/ExampleWording:
not: does not
IgnoredWords: []
+# Checks for `expect(...)` calls containing literal values.
+RSpec/ExpectActual:
+ Enabled: true
+
# Checks the file and folder naming of the spec file.
RSpec/FilePath:
Enabled: false
@@ -816,15 +987,51 @@ RSpec/Focus:
RSpec/InstanceVariable:
Enabled: false
+# Checks for `subject` definitions that come after `let` definitions.
+RSpec/LeadingSubject:
+ Enabled: false
+
+# Checks unreferenced `let!` calls being used for test setup.
+RSpec/LetSetup:
+ Enabled: false
+
+# Check that chains of messages are not being stubbed.
+RSpec/MessageChain:
+ Enabled: false
+
+# Checks that message expectations are set using spies.
+RSpec/MessageSpies:
+ Enabled: false
+
# Checks for multiple top-level describes.
RSpec/MultipleDescribes:
Enabled: false
+# Checks if examples contain too many `expect` calls.
+RSpec/MultipleExpectations:
+ Enabled: false
+
+# Checks for explicitly referenced test subjects.
+RSpec/NamedSubject:
+ Enabled: false
+
+# Checks for nested example groups.
+RSpec/NestedGroups:
+ Enabled: false
+
# Enforces the usage of the same method on all negative message expectations.
RSpec/NotToNot:
EnforcedStyle: not_to
Enabled: true
+# Check for repeated description strings in example groups.
+RSpec/RepeatedDescription:
+ Enabled: false
+
+# Checks for stubbed test subjects.
+RSpec/SubjectStub:
+ Enabled: false
+
# Prefer using verifying doubles over normal doubles.
RSpec/VerifiedDoubles:
Enabled: false
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index d581610162f..c24142c0a11 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -1,87 +1,13 @@
# This configuration was generated by
# `rubocop --auto-gen-config --exclude-limit 0`
-# on 2017-01-11 09:38:25 +0000 using RuboCop version 0.46.0.
+# on 2017-02-22 13:02:35 -0600 using RuboCop version 0.47.1.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
-# Offense count: 27
-# Configuration parameters: Include.
-# Include: **/Gemfile, **/gems.rb
-Bundler/OrderedGems:
- Enabled: false
-
-# Offense count: 175
-Lint/AmbiguousRegexpLiteral:
- Enabled: false
-
-# Offense count: 53
-# Configuration parameters: AllowSafeAssignment.
-Lint/AssignmentInCondition:
- Enabled: false
-
-# Offense count: 1
-Lint/EmptyWhen:
- Enabled: false
-
-# Offense count: 20
-Lint/HandleExceptions:
- Enabled: false
-
-# Offense count: 1
-Lint/Loop:
- Enabled: false
-
-# Offense count: 27
-Lint/ShadowingOuterLocalVariable:
- Enabled: false
-
-# Offense count: 10
-# Cop supports --auto-correct.
-Lint/UnifiedInteger:
- Enabled: false
-
-# Offense count: 21
-# Cop supports --auto-correct.
-Lint/UnneededSplatExpansion:
- Enabled: false
-
-# Offense count: 82
-# Cop supports --auto-correct.
-# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments.
-Lint/UnusedBlockArgument:
- Enabled: false
-
-# Offense count: 173
-# Cop supports --auto-correct.
-# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods.
-Lint/UnusedMethodArgument:
- Enabled: false
-
-# Offense count: 93
-# Configuration parameters: CountComments.
-Metrics/BlockLength:
- Enabled: false
-
-# Offense count: 3
-# Cop supports --auto-correct.
-Performance/RedundantBlockCall:
- Enabled: false
-
-# Offense count: 5
-# Cop supports --auto-correct.
-Performance/RedundantMatch:
- Enabled: false
-
-# Offense count: 32
-# Cop supports --auto-correct.
-# Configuration parameters: MaxKeyValuePairs.
-Performance/RedundantMerge:
- Enabled: false
-
-# Offense count: 7
-RSpec/BeEql:
+# Offense count: 51
+RSpec/BeforeAfterAll:
Enabled: false
# Offense count: 15
@@ -89,11 +15,11 @@ RSpec/BeEql:
RSpec/EmptyExampleGroup:
Enabled: false
-# Offense count: 24
-RSpec/ExpectActual:
+# Offense count: 1
+RSpec/ExpectOutput:
Enabled: false
-# Offense count: 58
+# Offense count: 63
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: implicit, each, example
RSpec/HookArgument:
@@ -105,154 +31,59 @@ RSpec/HookArgument:
RSpec/ImplicitExpect:
Enabled: false
-# Offense count: 237
-RSpec/LeadingSubject:
- Enabled: false
-
-# Offense count: 253
-RSpec/LetSetup:
- Enabled: false
-
-# Offense count: 13
-RSpec/MessageChain:
- Enabled: false
-
-# Offense count: 479
-# Configuration parameters: EnforcedStyle, SupportedStyles.
-# SupportedStyles: have_received, receive
-RSpec/MessageSpies:
- Enabled: false
-
-# Offense count: 3036
-RSpec/MultipleExpectations:
- Enabled: false
-
-# Offense count: 2133
-RSpec/NamedSubject:
- Enabled: false
-
-# Offense count: 1974
-# Configuration parameters: MaxNesting.
-RSpec/NestedGroups:
+# Offense count: 36
+RSpec/RepeatedExample:
Enabled: false
-# Offense count: 32
-RSpec/RepeatedDescription:
+# Offense count: 34
+RSpec/ScatteredSetup:
Enabled: false
# Offense count: 1
RSpec/SingleArgumentMessageChain:
Enabled: false
-# Offense count: 133
-RSpec/SubjectStub:
- Enabled: false
-
-# Offense count: 104
-# Cop supports --auto-correct.
-# Configuration parameters: Whitelist.
-# Whitelist: find_by_sql
-Rails/DynamicFindBy:
- Enabled: false
-
-# Offense count: 932
-# Cop supports --auto-correct.
-# Configuration parameters: Include.
-# Include: spec/**/*, test/**/*
-Rails/HttpPositionalArguments:
+# Offense count: 163
+Rails/FilePath:
Enabled: false
-# Offense count: 55
-Rails/OutputSafety:
- Enabled: false
-
-# Offense count: 182
-# Configuration parameters: EnforcedStyle, SupportedStyles.
-# SupportedStyles: strict, flexible
-Rails/TimeZone:
- Enabled: false
-
-# Offense count: 15
-# Cop supports --auto-correct.
+# Offense count: 2
# Configuration parameters: Include.
-# Include: app/models/**/*.rb
-Rails/Validation:
+# Include: db/migrate/*.rb
+Rails/ReversibleMigration:
Enabled: false
-# Offense count: 8
-# Cop supports --auto-correct.
-# Configuration parameters: AutoCorrect.
-Security/JSONLoad:
- Enabled: false
-
-# Offense count: 346
-# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
-# SupportedStyles: with_first_parameter, with_fixed_indentation
-Style/AlignParameters:
+# Offense count: 278
+# Configuration parameters: Blacklist.
+# Blacklist: decrement!, decrement_counter, increment!, increment_counter, toggle!, touch, update_all, update_attribute, update_column, update_columns, update_counters
+Rails/SkipsModelValidations:
Enabled: false
-# Offense count: 27
+# Offense count: 7
# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles.
-# SupportedStyles: always, conditionals
-Style/AndOr:
+Security/YAMLLoad:
Enabled: false
-# Offense count: 54
+# Offense count: 55
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: percent_q, bare_percent
Style/BarePercentLiterals:
Enabled: false
-# Offense count: 358
-# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles.
-# SupportedStyles: braces, no_braces, context_dependent
-Style/BracesAroundHashParameters:
- Enabled: false
-
-# Offense count: 6
-Style/CaseEquality:
- Enabled: false
-
-# Offense count: 37
-# Cop supports --auto-correct.
-Style/ColonMethodCall:
- Enabled: false
-
-# Offense count: 4
-# Cop supports --auto-correct.
-# Configuration parameters: Keywords.
-# Keywords: TODO, FIXME, OPTIMIZE, HACK, REVIEW
-Style/CommentAnnotation:
- Enabled: false
-
-# Offense count: 29
-# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles, SingleLineConditionsOnly.
-# SupportedStyles: assign_to_condition, assign_inside_condition
-Style/ConditionalAssignment:
- Enabled: false
-
-# Offense count: 1210
+# Offense count: 1304
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: leading, trailing
Style/DotPosition:
Enabled: false
-# Offense count: 18
-Style/DoubleNegation:
- Enabled: false
-
-# Offense count: 7
+# Offense count: 6
# Cop supports --auto-correct.
Style/EachWithObject:
Enabled: false
-# Offense count: 24
+# Offense count: 25
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: empty, nil, both
@@ -264,14 +95,14 @@ Style/EmptyElse:
Style/EmptyLiteral:
Enabled: false
-# Offense count: 57
+# Offense count: 56
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: compact, expanded
Style/EmptyMethod:
Enabled: false
-# Offense count: 147
+# Offense count: 184
# Cop supports --auto-correct.
# Configuration parameters: AllowForAlignment, ForceEqualSignAlignment.
Style/ExtraSpacing:
@@ -283,50 +114,50 @@ Style/ExtraSpacing:
Style/FormatString:
Enabled: false
-# Offense count: 238
+# Offense count: 268
# Configuration parameters: MinBodyLength.
Style/GuardClause:
Enabled: false
-# Offense count: 11
+# Offense count: 14
Style/IfInsideElse:
Enabled: false
-# Offense count: 173
+# Offense count: 179
# Cop supports --auto-correct.
# Configuration parameters: MaxLineLength.
Style/IfUnlessModifier:
Enabled: false
-# Offense count: 55
+# Offense count: 57
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
# SupportedStyles: special_inside_parentheses, consistent, align_brackets
Style/IndentArray:
Enabled: false
-# Offense count: 101
+# Offense count: 120
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
# SupportedStyles: special_inside_parentheses, consistent, align_braces
Style/IndentHash:
Enabled: false
-# Offense count: 41
+# Offense count: 45
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: line_count_dependent, lambda, literal
Style/Lambda:
Enabled: false
-# Offense count: 5
+# Offense count: 7
# Cop supports --auto-correct.
Style/LineEndConcatenation:
Enabled: false
-# Offense count: 19
+# Offense count: 22
# Cop supports --auto-correct.
-Style/MethodCallParentheses:
+Style/MethodCallWithoutArgsParentheses:
Enabled: false
# Offense count: 9
@@ -338,61 +169,49 @@ Style/MethodMissing:
Style/MultilineIfModifier:
Enabled: false
-# Offense count: 179
-# Cop supports --auto-correct.
-Style/MutableConstant:
- Enabled: false
-
-# Offense count: 8
+# Offense count: 22
# Cop supports --auto-correct.
Style/NestedParenthesizedCalls:
Enabled: false
-# Offense count: 13
+# Offense count: 17
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles.
# SupportedStyles: skip_modifier_ifs, always
Style/Next:
Enabled: false
-# Offense count: 19
+# Offense count: 31
# Cop supports --auto-correct.
# Configuration parameters: EnforcedOctalStyle, SupportedOctalStyles.
# SupportedOctalStyles: zero_with_o, zero_only
Style/NumericLiteralPrefix:
Enabled: false
-# Offense count: 19
+# Offense count: 77
# Cop supports --auto-correct.
# Configuration parameters: AutoCorrect, EnforcedStyle, SupportedStyles.
# SupportedStyles: predicate, comparison
Style/NumericPredicate:
Enabled: false
-# Offense count: 34
+# Offense count: 36
# Cop supports --auto-correct.
Style/ParallelAssignment:
Enabled: false
-# Offense count: 417
+# Offense count: 477
# Cop supports --auto-correct.
# Configuration parameters: PreferredDelimiters.
Style/PercentLiteralDelimiters:
Enabled: false
-# Offense count: 10
-# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles.
-# SupportedStyles: lower_case_q, upper_case_q
-Style/PercentQLiterals:
- Enabled: false
-
-# Offense count: 13
+# Offense count: 14
# Cop supports --auto-correct.
Style/PerlBackrefs:
Enabled: false
-# Offense count: 64
+# Offense count: 72
# Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist.
# NamePrefix: is_, has_, have_
# NamePrefixBlacklist: is_, has_, have_
@@ -400,7 +219,7 @@ Style/PerlBackrefs:
Style/PredicateName:
Enabled: false
-# Offense count: 33
+# Offense count: 39
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: short, verbose
@@ -412,7 +231,7 @@ Style/PreferredHashMethods:
Style/Proc:
Enabled: false
-# Offense count: 50
+# Offense count: 62
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: compact, exploded
@@ -424,35 +243,30 @@ Style/RaiseArgs:
Style/RedundantBegin:
Enabled: false
-# Offense count: 1
-# Cop supports --auto-correct.
-Style/RedundantException:
- Enabled: false
-
-# Offense count: 29
+# Offense count: 32
# Cop supports --auto-correct.
Style/RedundantFreeze:
Enabled: false
-# Offense count: 11
+# Offense count: 15
# Cop supports --auto-correct.
# Configuration parameters: AllowMultipleReturnValues.
Style/RedundantReturn:
Enabled: false
-# Offense count: 359
+# Offense count: 365
# Cop supports --auto-correct.
Style/RedundantSelf:
Enabled: false
-# Offense count: 105
+# Offense count: 108
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes.
# SupportedStyles: slashes, percent_r, mixed
Style/RegexpLiteral:
Enabled: false
-# Offense count: 19
+# Offense count: 22
# Cop supports --auto-correct.
Style/RescueModifier:
Enabled: false
@@ -462,19 +276,13 @@ Style/RescueModifier:
Style/SelfAssignment:
Enabled: false
-# Offense count: 2
-# Configuration parameters: Methods.
-# Methods: {"reduce"=>["acc", "elem"]}, {"inject"=>["acc", "elem"]}
-Style/SingleLineBlockParams:
- Enabled: false
-
# Offense count: 50
# Cop supports --auto-correct.
# Configuration parameters: AllowIfMethodIsEmpty.
Style/SingleLineMethods:
Enabled: false
-# Offense count: 138
+# Offense count: 155
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: space, no_space
@@ -487,26 +295,22 @@ Style/SpaceBeforeBlockBraces:
Style/SpaceBeforeFirstArg:
Enabled: false
-# Offense count: 37
+# Offense count: 38
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: require_no_space, require_space
Style/SpaceInLambdaLiteral:
Enabled: false
-# Offense count: 174
+# Offense count: 203
# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters.
+# Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SupportedStylesForEmptyBraces, SpaceBeforeBlockParameters.
# SupportedStyles: space, no_space
+# SupportedStylesForEmptyBraces: space, no_space
Style/SpaceInsideBlockBraces:
Enabled: false
-# Offense count: 115
-# Cop supports --auto-correct.
-Style/SpaceInsideBrackets:
- Enabled: false
-
-# Offense count: 77
+# Offense count: 91
# Cop supports --auto-correct.
Style/SpaceInsideParens:
Enabled: false
@@ -516,21 +320,21 @@ Style/SpaceInsideParens:
Style/SpaceInsidePercentLiteralDelimiters:
Enabled: false
-# Offense count: 53
+# Offense count: 55
# Cop supports --auto-correct.
# Configuration parameters: SupportedStyles.
# SupportedStyles: use_perl_names, use_english_names
Style/SpecialGlobalVars:
EnforcedStyle: use_perl_names
-# Offense count: 25
+# Offense count: 40
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: single_quotes, double_quotes
Style/StringLiteralsInInterpolation:
Enabled: false
-# Offense count: 54
+# Offense count: 57
# Cop supports --auto-correct.
# Configuration parameters: IgnoredMethods.
# IgnoredMethods: respond_to, define_method
@@ -544,27 +348,20 @@ Style/SymbolProc:
Style/TernaryParentheses:
Enabled: false
-# Offense count: 36
+# Offense count: 43
# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyleForMultiline, SupportedStyles.
-# SupportedStyles: comma, consistent_comma, no_comma
+# Configuration parameters: EnforcedStyleForMultiline, SupportedStylesForMultiline.
+# SupportedStylesForMultiline: comma, consistent_comma, no_comma
Style/TrailingCommaInArguments:
Enabled: false
-# Offense count: 150
-# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyleForMultiline, SupportedStyles.
-# SupportedStyles: comma, consistent_comma, no_comma
-Style/TrailingCommaInLiteral:
- Enabled: false
-
-# Offense count: 7
+# Offense count: 13
# Cop supports --auto-correct.
# Configuration parameters: AllowNamedUnderscoreVariables.
Style/TrailingUnderscoreVariable:
Enabled: false
-# Offense count: 67
+# Offense count: 70
# Cop supports --auto-correct.
Style/TrailingWhitespace:
Enabled: false
@@ -576,12 +373,12 @@ Style/TrailingWhitespace:
Style/TrivialAccessors:
Enabled: false
-# Offense count: 2
+# Offense count: 6
# Cop supports --auto-correct.
Style/UnlessElse:
Enabled: false
-# Offense count: 17
+# Offense count: 22
# Cop supports --auto-correct.
Style/UnneededInterpolation:
Enabled: false
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 71d38e5453d..7f5b101ad6b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,209 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 8.17.0 (2017-02-22)
+
+- API: Fix file downloading. !0 (8267)
+- Changed composer installer script in the CI PHP example doc. !4342 (Jeffrey Cafferata)
+- Display fullscreen button on small screens. !5302 (winniehell)
+- Add system hook for when a project is updated (other than rename/transfer). !5711 (Tommy Beadle)
+- Fix notifications when set at group level. !6813 (Alexandre Maia)
+- Project labels can now be promoted to group labels. !7242 (Olaf Tomalka)
+- use webpack to bundle frontend assets and use karma for frontend testing. !7288
+- Adds back ability to stop all environments. !7379
+- Added labels empty state. !7443
+- Add ability to define a coverage regex in the .gitlab-ci.yml. !7447 (Leandro Camargo)
+- Disable automatic login after clicking email confirmation links. !7472
+- Search feature: redirects to commit page if query is commit sha and only commit found. !8028 (YarNayar)
+- Create a TODO for user who set auto-merge when a build fails, merge conflict occurs. !8056 (twonegatives)
+- Don't group issues by project on group-level and dashboard issue indexes. !8111 (Bernardo Castro)
+- Mark MR as WIP when pushing WIP commits. !8124 (Jurre Stender @jurre)
+- Flag multiple empty lines in eslint, fix offenses. !8137
+- Add sorting pipeline for a commit. !8319 (Takuya Noguchi)
+- Adds service trigger events to api. !8324
+- Update pipeline and commit links when CI status is updated. !8351
+- Hide version check image if there is no internet connection. !8355 (Ken Ding)
+- Prevent removal of input fields if it is the parent dropdown element. !8397
+- Introduce maximum session time for terminal websocket connection. !8413
+- Allow creating protected branches when user can merge to such branch. !8458
+- Refactor MergeRequests::BuildService. !8462 (Rydkin Maxim)
+- Added GitLab Pages to CE. !8463
+- Support notes when a project is not specified (personal snippet notes). !8468
+- Use warning icon in mini-graph if stage passed conditionally. !8503
+- Don’t count tasks that are not defined as list items correctly. !8526
+- Reformat messages ChatOps. !8528
+- Copy commit SHA to clipboard. !8547
+- Improve button accessibility on pipelines page. !8561
+- Display project ID in project settings. !8572 (winniehell)
+- PlantUML support for Markdown. !8588 (Horacio Sanson)
+- Fix reply by email without sub-addressing for some clients from Microsoft and Apple. !8620
+- Fix nested tasks in ordered list. !8626
+- Fix Sort by Recent Sign-in in Admin Area. !8637 (Poornima M)
+- Avoid repeated dashes in $CI_ENVIRONMENT_SLUG. !8638
+- Only show Merge Request button when user can create a MR. !8639
+- Prevent copying of line numbers in parallel diff view. !8706
+- Improve build policy and access abilities. !8711
+- API: Remove /projects/:id/keys/.. endpoints. !8716 (Robert Schilling)
+- API: Remove deprecated 'expires_at' from project snippets. !8723 (Robert Schilling)
+- Add `copy` backup strategy to combat file changed errors. !8728
+- adds avatar for discussion note. !8734
+- Add link verification to badge partial in order to render a badge without a link. !8740
+- Reduce hits to LDAP on Git HTTP auth by reordering auth mechanisms. !8752
+- prevent diff unfolding link from appearing when there are no more lines to show. !8761
+- Redesign searchbar in admin project list. !8776
+- Rename Builds to Pipelines, CI/CD Pipelines, or Jobs everywhere. !8787
+- dismiss sidebar on repo buttons click. !8798 (Adam Pahlevi)
+- fixed small mini pipeline graph line glitch. !8804
+- Make all system notes lowercase. !8807
+- Support unauthenticated LFS object downloads for public projects. !8824 (Ben Boeckel)
+- Add read-only full_path and full_name attributes to Group API. !8827
+- allow relative url change without recompiling frontend assets. !8831
+- Use vue.js Pipelines table in commit and merge request view. !8844
+- Use reCaptcha when an issue is identified as a spam. !8846
+- resolve deprecation warnings. !8855 (Adam Pahlevi)
+- Cop for gem fetched from a git source. !8856 (Adam Pahlevi)
+- Remove flash warning from login page. !8864 (Gerald J. Padilla)
+- Adds documentation for how to use Vue.js. !8866
+- Add 'View on [env]' link to blobs and individual files in diffs. !8867
+- Replace word user with member. !8872
+- Change the reply shortcut to focus the field even without a selection. !8873 (Brian Hall)
+- Unify MR diff file button style. !8874
+- Unify projects search by removing /projects/:search endpoint. !8877
+- Fix disable storing of sensitive information when importing a new repo. !8885 (Bernard Pietraga)
+- Fix pipeline graph vertical spacing in Firefox and Safari. !8886
+- Fix filtered search user autocomplete for gitlab instances that are hosted on a subdirectory. !8891
+- Fix Ctrl+Click support for Todos and Merge Request page tabs. !8898
+- Fix wrong call to ProjectCacheWorker.perform. !8910
+- Don't perform Devise trackable updates on blocked User records. !8915
+- Add ability to export project inherited group members to Import/Export. !8923
+- replace `find_with_namespace` with `find_by_full_path`. !8949 (Adam Pahlevi)
+- Fixes flickering of avatar border in mention dropdown. !8950
+- Remove unnecessary queries for .atom and .json in Dashboard::ProjectsController#index. !8956
+- Fix deleting projects with pipelines and builds. !8960
+- Fix broken anchor links when special characters are used. !8961 (Andrey Krivko)
+- Ensure autogenerated title does not cause failing spec. !8963 (brian m. carlson)
+- Update doc for enabling or disabling GitLab CI. !8965 (Takuya Noguchi)
+- Remove deprecated MR and Issue endpoints and preserve V3 namespace. !8967
+- Fixed "substract" typo on /help/user/project/slash_commands. !8976 (Jason Aquino)
+- Preserve backward compatibility CI/CD and disallow setting `coverage` regexp in global context. !8981
+- use babel to transpile all non-vendor javascript assets regardless of file extension. !8988
+- Fix MR widget url. !8989
+- Fixes hover cursor on pipeline pagenation. !9003
+- Layer award emoji dropdown over the right sidebar. !9004
+- Do not display deploy keys in user's own ssh keys list. !9024
+- upgrade babel 5.8.x to babel 6.22.x. !9072
+- upgrade to webpack v2.2. !9078
+- Trigger autocomplete after selecting a slash command. !9117
+- Add space between text and loading icon in Megre Request Widget. !9119
+- Fix job to pipeline renaming. !9147
+- Replace static fixture for merge_request_tabs_spec.js. !9172 (winniehell)
+- Replace static fixture for right_sidebar_spec.js. !9211 (winniehell)
+- Show merge errors in merge request widget. !9229
+- Increase process_commit queue weight from 2 to 3. !9326 (blackst0ne)
+- Don't require lib/gitlab/request_profiler/middleware.rb in config/initializers/request_profiler.rb.
+- Force new password after password reset via API. (George Andrinopoulos)
+- Allows to search within project by commit hash. (YarNayar)
+- Show organisation membership and delete comment on smaller viewports, plus change comment author name to username.
+- Remove turbolinks.
+- Convert pipeline action icons to svg to have them propperly positioned.
+- Remove rogue scrollbars for issue comments with inline elements.
+- Align Segoe UI label text.
+- Color + and - signs in diffs to increase code legibility.
+- Fix tab index order on branch commits list page. (Ryan Harris)
+- Add hover style to copy icon on commit page header. (Ryan Harris)
+- Remove hover animation from row elements.
+- Improve pipeline status icon linking in widgets.
+- Fix commit title bar and repository view copy clipboard button order on last commit in repository view.
+- Fix mini-pipeline stage tooltip text wrapping.
+- Updated builds info link on the project settings page. (Ryan Harris)
+- 27240 Make progress bars consistent.
+- Only render hr when user can't archive project.
+- 27352-search-label-filter-header.
+- Include :author, :project, and :target in Event.with_associations.
+- Don't instantiate AR objects in Event.in_projects.
+- Don't capitalize environment name in show page.
+- Update and pin the `jwt` gem to ~> 1.5.6.
+- Edited the column header for the environments list from created to updated and added created to environments detail page colum header titles.
+- Give ci status text on pipeline graph a better font-weight.
+- Add default labels to bulk assign dropdowns.
+- Only return target project's comments for a commit.
+- Fixes Pipelines table is not showing branch name for commit.
+- Fix regression where cmd-click stopped working for todos and merge request tabs.
+- Fix stray pipelines API request when showing MR.
+- Fix Merge request pipelines displays JSON.
+- Fix current build arrow indicator.
+- Fix contribution activity alignment.
+- Show Pipeline(not Job) in MR desktop notification.
+- Fix tooltips in mini pipeline graph.
+- Display loading indicator when filtering ref switcher dropdown.
+- Show pipeline graph in MR widget if there are any stages.
+- Fix icon colors in merge request widget mini graph.
+- Improve blockquote formatting in notification emails.
+- Adds container to tooltip in order to make it work with overflow:hidden in parent element.
+- Restore pagination to admin abuse reports.
+- Ensure export files are removed after a namespace is deleted.
+- Add `y` keyboard shortcut to move to file permalink.
+- Adds /target_branch slash command functionality for merge requests. (YarNayar)
+- Patch Asciidocs rendering to block XSS.
+- contribution calendar scrolls from right to left.
+- Copying a rendered issue/comment will paste into GFM textareas as actual GFM.
+- Don't delete assigned MRs/issues when user is deleted.
+- Remove new branch button for confidential issues.
+- Don't allow project guests to subscribe to merge requests through the API. (Robert Schilling)
+- Don't connect in Gitlab::Database.adapter_name.
+- Prevent users from creating notes on resources they can't access.
+- Ignore encrypted attributes in Import/Export.
+- Change rspec test to guarantee window is resized before visiting page.
+- Prevent users from deleting system deploy keys via the project deploy key API.
+- Fix XSS vulnerability in SVG attachments.
+- Make MR-review-discussions more reliable.
+- fix incorrect sidekiq concurrency count in admin background page. (wendy0402)
+- Make notification_service spec DRYer by making test reusable. (YarNayar)
+- Redirect http://someproject.git to http://someproject. (blackst0ne)
+- Fixed group label links in issue/merge request sidebar.
+- Improve gl.utils.handleLocationHash tests.
+- Fixed Issuable sidebar not closing on smaller/mobile sized screens.
+- Resets assignee dropdown when sidebar is open.
+- Disallow system notes for closed issuables.
+- Fix timezone on issue boards due date.
+- Remove unused js response from refs controller.
+- Prevent the GitHub importer from assigning labels and comments to merge requests or issues belonging to other projects.
+- Fixed merge requests tab extra margin when fixed to window.
+- Patch XSS vulnerability in RDOC support.
+- Refresh authorizations when transferring projects.
+- Remove issue and MR counts from labels index.
+- Don't use backup Active Record connections for Sidekiq.
+- Add index to ci_trigger_requests for commit_id.
+- Add indices to improve loading of labels page.
+- Reduced query count for snippet search.
+- Update GitLab Pages to v0.3.1.
+- Upgrade omniauth gem to 1.3.2.
+- Remove deprecated GitlabCiService.
+- Requeue pending deletion projects.
+
+## 8.16.6 (2017-02-17)
+
+- API: Fix file downloading. !0 (8267)
+- Reduce hits to LDAP on Git HTTP auth by reordering auth mechanisms. !8752
+- Fix filtered search user autocomplete for gitlab instances that are hosted on a subdirectory. !8891
+- Fix wrong call to ProjectCacheWorker.perform. !8910
+- Remove unnecessary queries for .atom and .json in Dashboard::ProjectsController#index. !8956
+- Fix broken anchor links when special characters are used. !8961 (Andrey Krivko)
+- Do not display deploy keys in user's own ssh keys list. !9024
+- Show merge errors in merge request widget. !9229
+- Don't delete assigned MRs/issues when user is deleted.
+- backport of EE fix !954.
+- Refresh authorizations when transferring projects.
+- Don't use backup Active Record connections for Sidekiq.
+- Check public snippets for spam.
+
+## 8.16.5 (2017-02-14)
+
+- Patch Asciidocs rendering to block XSS.
+- Fix XSS vulnerability in SVG attachments.
+- Prevent the GitHub importer from assigning labels and comments to merge requests or issues belonging to other projects.
+- Patch XSS vulnerability in RDOC support.
+
## 8.16.4 (2017-02-02)
- Support non-ASCII characters in GFM autocomplete. !8729
@@ -174,6 +377,17 @@ entry.
- Add margin to markdown math blocks.
- Add hover state to MR comment reply button.
+## 8.15.7 (2017-02-15)
+
+- No changes.
+
+## 8.15.6 (2017-02-14)
+
+- Patch Asciidocs rendering to block XSS.
+- Fix XSS vulnerability in SVG attachments.
+- Prevent the GitHub importer from assigning labels and comments to merge requests or issues belonging to other projects.
+- Patch XSS vulnerability in RDOC support.
+
## 8.15.4 (2017-01-09)
- Make successful pipeline emails off for watchers. !8176
@@ -437,6 +651,17 @@ entry.
- Whitelist next project names: help, ci, admin, search. !8227
- Adds back CSS for progress-bars. !8237
+## 8.14.10 (2017-02-15)
+
+- No changes.
+
+## 8.14.9 (2017-02-14)
+
+- Patch Asciidocs rendering to block XSS.
+- Fix XSS vulnerability in SVG attachments.
+- Prevent the GitHub importer from assigning labels and comments to merge requests or issues belonging to other projects.
+- Patch XSS vulnerability in RDOC support.
+
## 8.14.8 (2017-01-25)
- Accept environment variables from the `pre-receive` script. !7967
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 315cd1e598c..eed63127d9f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -15,7 +15,7 @@
- [Issue weight](#issue-weight)
- [Regression issues](#regression-issues)
- [Technical debt](#technical-debt)
- - [Stewardship][#stewardship]
+ - [Stewardship](#stewardship)
- [Merge requests](#merge-requests)
- [Merge request guidelines](#merge-request-guidelines)
- [Contribution acceptance criteria](#contribution-acceptance-criteria)
@@ -93,18 +93,20 @@ Please see the [UX Guide for GitLab].
### Retrospective
-After each release (usually on the 22nd of each month), we have a retrospective
-call where we discuss what went well, what went wrong, and what we can improve
-for the next release. The [retrospective notes] are public and you are invited
-to comment them.
-If you're interested, you can even join the [retrospective call][retro-kickoff-call].
+After each release, we have a retrospective call where we discuss what went well,
+what went wrong, and what we can improve for the next release. The
+[retrospective notes] are public and you are invited to comment on them.
+If you're interested, you can even join the
+[retrospective call][retro-kickoff-call], on the first working day after the
+22nd at 6pm CET / 9am PST.
### Kickoff
-Before working on the next release (usually on the 8th of each month), we have a
+Before working on the next release, we have a
kickoff call to explain what we expect to ship in the next release. The
-[kickoff notes] are public and you are invited to comment them.
-If you're interested, you can even join the [kickoff call][retro-kickoff-call].
+[kickoff notes] are public and you are invited to comment on them.
+If you're interested, you can even join the [kickoff call][retro-kickoff-call],
+on the first working day after the 7th at 6pm CET / 9am PST..
[retrospective notes]: https://docs.google.com/document/d/1nEkM_7Dj4bT21GJy0Ut3By76FZqCfLBmFQNVThmW2TY/edit?usp=sharing
[kickoff notes]: https://docs.google.com/document/d/1ElPkZ90A8ey_iOkTvUs_ByMlwKK6NAB2VOK5835wYK0/edit?usp=sharing
@@ -424,13 +426,13 @@ merge request:
1. [Ruby](https://github.com/bbatsov/ruby-style-guide).
Important sections include [Source Code Layout][rss-source] and
[Naming][rss-naming]. Use:
- - multi-line method chaining style **Option B**: dot `.` on previous line
+ - multi-line method chaining style **Option A**: dot `.` on the second line
- string literal quoting style **Option A**: single quoted by default
1. [Rails](https://github.com/bbatsov/rails-style-guide)
1. [Newlines styleguide][newlines-styleguide]
1. [Testing](doc/development/testing.md)
1. [JavaScript (ES6)](https://github.com/airbnb/javascript)
-1. [JavaScript (ES5)](https://github.com/airbnb/javascript/tree/master/es5)
+1. [JavaScript (ES5)](https://github.com/airbnb/javascript/tree/es5-deprecated/es5)
1. [SCSS styleguide][scss-styleguide]
1. [Shell commands](doc/development/shell_commands.md) created by GitLab
contributors to enhance security
diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION
index abd410582de..d15723fbe8d 100644
--- a/GITLAB_PAGES_VERSION
+++ b/GITLAB_PAGES_VERSION
@@ -1 +1 @@
-0.2.4
+0.3.2
diff --git a/Gemfile b/Gemfile
index 4aff9cd396c..8715acba24f 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,6 +1,6 @@
source 'https://rubygems.org'
-gem 'rails', '4.2.7.1'
+gem 'rails', '4.2.8'
gem 'rails-deprecated_sanitizer', '~> 1.0.3'
# Responders respond_to and respond_with
@@ -29,11 +29,12 @@ gem 'omniauth-github', '~> 1.1.1'
gem 'omniauth-gitlab', '~> 1.0.2'
gem 'omniauth-google-oauth2', '~> 0.4.1'
gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
+gem 'omniauth-oauth2-generic', '~> 0.2.2'
gem 'omniauth-saml', '~> 1.7.0'
gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd', '~> 2.2.0'
-gem 'omniauth-authentiq', '~> 0.2.0'
+gem 'omniauth-authentiq', '~> 0.3.0'
gem 'rack-oauth2', '~> 1.2.1'
gem 'jwt', '~> 1.5.6'
@@ -200,7 +201,7 @@ gem 'babosa', '~> 1.0.2'
gem 'loofah', '~> 2.0.3'
# Working with license
-gem 'licensee', '~> 8.0.0'
+gem 'licensee', '~> 8.7.0'
# Protect against bruteforcing
gem 'rack-attack', '~> 4.4.1'
@@ -284,7 +285,7 @@ group :development, :test do
gem 'rspec-retry', '~> 0.4.5'
gem 'spinach-rails', '~> 0.2.1'
gem 'spinach-rerun-reporter', '~> 0.0.2'
- gem 'rspec_profiling'
+ gem 'rspec_profiling', '~> 0.0.5'
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
gem 'minitest', '~> 5.7.0'
@@ -300,10 +301,10 @@ group :development, :test do
gem 'spring-commands-rspec', '~> 1.0.4'
gem 'spring-commands-spinach', '~> 1.1.0'
- gem 'rubocop', '~> 0.46.0', require: false
- gem 'rubocop-rspec', '~> 1.9.1', require: false
+ gem 'rubocop', '~> 0.47.1', require: false
+ gem 'rubocop-rspec', '~> 1.12.0', require: false
gem 'scss_lint', '~> 0.47.0', require: false
- gem 'haml_lint', '~> 0.18.2', require: false
+ gem 'haml_lint', '~> 0.21.0', require: false
gem 'simplecov', '0.12.0', require: false
gem 'flay', '~> 2.6.1', require: false
gem 'bundler-audit', '~> 0.5.0', require: false
@@ -332,7 +333,7 @@ gem 'newrelic_rpm', '~> 3.16'
gem 'octokit', '~> 4.6.2'
-gem 'mail_room', '~> 0.9.0'
+gem 'mail_room', '~> 0.9.1'
gem 'email_reply_trimmer', '~> 0.1'
gem 'html2text'
diff --git a/Gemfile.lock b/Gemfile.lock
index c5dd6ab8d22..b46b09c7544 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -3,40 +3,39 @@ GEM
specs:
RedCloth (4.3.2)
ace-rails-ap (4.1.0)
- actionmailer (4.2.7.1)
- actionpack (= 4.2.7.1)
- actionview (= 4.2.7.1)
- activejob (= 4.2.7.1)
+ actionmailer (4.2.8)
+ actionpack (= 4.2.8)
+ actionview (= 4.2.8)
+ activejob (= 4.2.8)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 1.0, >= 1.0.5)
- actionpack (4.2.7.1)
- actionview (= 4.2.7.1)
- activesupport (= 4.2.7.1)
+ actionpack (4.2.8)
+ actionview (= 4.2.8)
+ activesupport (= 4.2.8)
rack (~> 1.6)
rack-test (~> 0.6.2)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
- actionview (4.2.7.1)
- activesupport (= 4.2.7.1)
+ actionview (4.2.8)
+ activesupport (= 4.2.8)
builder (~> 3.1)
erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5)
- rails-html-sanitizer (~> 1.0, >= 1.0.2)
- activejob (4.2.7.1)
- activesupport (= 4.2.7.1)
+ rails-html-sanitizer (~> 1.0, >= 1.0.3)
+ activejob (4.2.8)
+ activesupport (= 4.2.8)
globalid (>= 0.3.0)
- activemodel (4.2.7.1)
- activesupport (= 4.2.7.1)
+ activemodel (4.2.8)
+ activesupport (= 4.2.8)
builder (~> 3.1)
- activerecord (4.2.7.1)
- activemodel (= 4.2.7.1)
- activesupport (= 4.2.7.1)
+ activerecord (4.2.8)
+ activemodel (= 4.2.8)
+ activesupport (= 4.2.8)
arel (~> 6.0)
activerecord_sane_schema_dumper (0.2)
rails (>= 4, < 5)
- activesupport (4.2.7.1)
+ activesupport (4.2.8)
i18n (~> 0.7)
- json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
@@ -47,7 +46,7 @@ GEM
activerecord (>= 3.0)
akismet (2.0.0)
allocations (1.0.5)
- arel (6.0.3)
+ arel (6.0.4)
asana (0.4.0)
faraday (~> 0.9)
faraday_middleware (~> 0.9)
@@ -86,7 +85,7 @@ GEM
sass (>= 3.3.4)
brakeman (3.4.1)
browser (2.2.0)
- builder (3.2.2)
+ builder (3.2.3)
bullet (5.2.0)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.10.0)
@@ -127,7 +126,7 @@ GEM
execjs
coffee-script-source (1.10.0)
colorize (0.7.7)
- concurrent-ruby (1.0.2)
+ concurrent-ruby (1.0.4)
connection_pool (2.2.1)
crack (0.4.3)
safe_yaml (~> 1.0.0)
@@ -320,10 +319,10 @@ GEM
multi_json (>= 1.3.2)
haml (4.0.7)
tilt
- haml_lint (0.18.2)
+ haml_lint (0.21.0)
haml (~> 4.0)
- rake (>= 10, < 12)
- rubocop (>= 0.36.0)
+ rake (>= 10, < 13)
+ rubocop (>= 0.47.0)
sysexits (~> 1.1)
hamlit (2.6.1)
temple (~> 0.7.6)
@@ -354,7 +353,7 @@ GEM
json (~> 1.8)
multi_xml (>= 0.5.2)
httpclient (2.8.2)
- i18n (0.7.0)
+ i18n (0.8.0)
ice_nine (0.11.1)
influxdb (0.2.3)
cause
@@ -370,7 +369,7 @@ GEM
thor (>= 0.14, < 2.0)
jquery-ui-rails (5.0.5)
railties (>= 3.2.16)
- json (1.8.3)
+ json (1.8.6)
json-schema (2.6.2)
addressable (~> 2.3.8)
jwt (1.5.6)
@@ -399,8 +398,8 @@ GEM
rubyzip
thor
xml-simple
- licensee (8.0.0)
- rugged (>= 0.24b)
+ licensee (8.7.0)
+ rugged (~> 0.24)
little-plugger (1.1.4)
logging (2.1.0)
little-plugger (~> 1.1)
@@ -409,7 +408,7 @@ GEM
nokogiri (>= 1.5.9)
mail (2.6.4)
mime-types (>= 1.16, < 4)
- mail_room (0.9.0)
+ mail_room (0.9.1)
memoist (0.15.0)
method_source (0.8.2)
mime-types (2.99.3)
@@ -429,9 +428,8 @@ GEM
net-ssh (3.0.1)
netrc (0.11.0)
newrelic_rpm (3.16.0.318)
- nokogiri (1.6.8)
+ nokogiri (1.6.8.1)
mini_portile2 (~> 2.1.0)
- pkg-config (~> 1.1.7)
numerizer (0.1.1)
oauth (0.5.1)
oauth2 (1.2.0)
@@ -448,7 +446,7 @@ GEM
rack (>= 1.0, < 3)
omniauth-auth0 (1.4.1)
omniauth-oauth2 (~> 1.1)
- omniauth-authentiq (0.2.2)
+ omniauth-authentiq (0.3.0)
omniauth-oauth2 (~> 1.3, >= 1.3.1)
omniauth-azure-oauth2 (0.0.6)
jwt (~> 1.0)
@@ -483,6 +481,8 @@ GEM
omniauth-oauth2 (1.3.1)
oauth2 (~> 1.0)
omniauth (~> 1.2)
+ omniauth-oauth2-generic (0.2.2)
+ omniauth-oauth2 (~> 1.0)
omniauth-saml (1.7.0)
omniauth (~> 1.3)
ruby-saml (~> 1.4)
@@ -501,10 +501,9 @@ GEM
os (0.9.6)
paranoia (2.2.0)
activerecord (>= 4.0, < 5.1)
- parser (2.3.1.4)
+ parser (2.4.0.0)
ast (~> 2.2)
pg (0.18.4)
- pkg-config (1.1.7)
poltergeist (1.9.0)
capybara (~> 2.1)
cliver (~> 0.3.1)
@@ -546,28 +545,28 @@ GEM
rack
rack-test (0.6.3)
rack (>= 1.0)
- rails (4.2.7.1)
- actionmailer (= 4.2.7.1)
- actionpack (= 4.2.7.1)
- actionview (= 4.2.7.1)
- activejob (= 4.2.7.1)
- activemodel (= 4.2.7.1)
- activerecord (= 4.2.7.1)
- activesupport (= 4.2.7.1)
+ rails (4.2.8)
+ actionmailer (= 4.2.8)
+ actionpack (= 4.2.8)
+ actionview (= 4.2.8)
+ activejob (= 4.2.8)
+ activemodel (= 4.2.8)
+ activerecord (= 4.2.8)
+ activesupport (= 4.2.8)
bundler (>= 1.3.0, < 2.0)
- railties (= 4.2.7.1)
+ railties (= 4.2.8)
sprockets-rails
rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha)
- rails-dom-testing (1.0.7)
+ rails-dom-testing (1.0.8)
activesupport (>= 4.2.0.beta, < 5.0)
- nokogiri (~> 1.6.0)
+ nokogiri (~> 1.6)
rails-deprecated_sanitizer (>= 1.0.1)
rails-html-sanitizer (1.0.3)
loofah (~> 2.0)
- railties (4.2.7.1)
- actionpack (= 4.2.7.1)
- activesupport (= 4.2.7.1)
+ railties (4.2.8)
+ actionpack (= 4.2.8)
+ activesupport (= 4.2.8)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rainbow (2.1.0)
@@ -638,18 +637,18 @@ GEM
rspec-retry (0.4.5)
rspec-core
rspec-support (3.5.0)
- rspec_profiling (0.0.4)
+ rspec_profiling (0.0.5)
activerecord
pg
rails
sqlite3
- rubocop (0.46.0)
- parser (>= 2.3.1.1, < 3.0)
+ rubocop (0.47.1)
+ parser (>= 2.3.3.1, < 3.0)
powerpack (~> 0.1)
rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1)
- rubocop-rspec (1.9.1)
+ rubocop-rspec (1.12.0)
rubocop (>= 0.42.0)
ruby-fogbugz (0.2.1)
crack (~> 0.4)
@@ -731,14 +730,14 @@ GEM
spring (>= 0.9.1)
spring-commands-spinach (1.1.0)
spring (>= 0.9.1)
- sprockets (3.7.0)
+ sprockets (3.7.1)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
- sprockets-rails (3.1.1)
+ sprockets-rails (3.2.0)
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
- sqlite3 (1.3.11)
+ sqlite3 (1.3.13)
stackprof (0.2.10)
state_machines (0.4.0)
state_machines-activemodel (0.4.0)
@@ -758,9 +757,9 @@ GEM
daemons (~> 1.0, >= 1.0.9)
eventmachine (~> 1.0, >= 1.0.4)
rack (>= 1, < 3)
- thor (0.19.1)
+ thor (0.19.4)
thread_safe (0.3.5)
- tilt (2.0.5)
+ tilt (2.0.6)
timecop (0.8.1)
timfel-krb5-auth (0.8.3)
tool (0.2.3)
@@ -777,7 +776,7 @@ GEM
unf (0.1.4)
unf_ext
unf_ext (0.0.7.2)
- unicode-display_width (1.1.1)
+ unicode-display_width (1.1.3)
unicorn (5.1.0)
kgio (~> 2.6)
raindrops (~> 0.7)
@@ -889,7 +888,7 @@ DEPENDENCIES
google-api-client (~> 0.8.6)
grape (~> 0.18.0)
grape-entity (~> 0.6.0)
- haml_lint (~> 0.18.2)
+ haml_lint (~> 0.21.0)
hamlit (~> 2.6.1)
health_check (~> 2.2.0)
hipchat (~> 1.5.0)
@@ -908,9 +907,9 @@ DEPENDENCIES
kubeclient (~> 2.2.0)
letter_opener_web (~> 1.3.0)
license_finder (~> 2.1.0)
- licensee (~> 8.0.0)
+ licensee (~> 8.7.0)
loofah (~> 2.0.3)
- mail_room (~> 0.9.0)
+ mail_room (~> 0.9.1)
method_source (~> 0.8)
minitest (~> 5.7.0)
mousetrap-rails (~> 1.4.6)
@@ -923,7 +922,7 @@ DEPENDENCIES
oj (~> 2.17.4)
omniauth (~> 1.3.2)
omniauth-auth0 (~> 1.4.1)
- omniauth-authentiq (~> 0.2.0)
+ omniauth-authentiq (~> 0.3.0)
omniauth-azure-oauth2 (~> 0.0.6)
omniauth-cas3 (~> 1.1.2)
omniauth-facebook (~> 4.0.0)
@@ -931,6 +930,7 @@ DEPENDENCIES
omniauth-gitlab (~> 1.0.2)
omniauth-google-oauth2 (~> 0.4.1)
omniauth-kerberos (~> 0.3.0)
+ omniauth-oauth2-generic (~> 0.2.2)
omniauth-saml (~> 1.7.0)
omniauth-shibboleth (~> 1.2.0)
omniauth-twitter (~> 1.2.0)
@@ -946,7 +946,7 @@ DEPENDENCIES
rack-cors (~> 0.4.0)
rack-oauth2 (~> 1.2.1)
rack-proxy (~> 0.6.0)
- rails (= 4.2.7.1)
+ rails (= 4.2.8)
rails-deprecated_sanitizer (~> 1.0.3)
rainbow (~> 2.1.0)
rblineprof (~> 0.3.6)
@@ -962,9 +962,9 @@ DEPENDENCIES
rqrcode-rails3 (~> 0.1.7)
rspec-rails (~> 3.5.0)
rspec-retry (~> 0.4.5)
- rspec_profiling
- rubocop (~> 0.46.0)
- rubocop-rspec (~> 1.9.1)
+ rspec_profiling (~> 0.0.5)
+ rubocop (~> 0.47.1)
+ rubocop-rspec (~> 1.12.0)
ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 0.16.2)
rugged (~> 0.24.0)
@@ -1011,4 +1011,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
- 1.14.2
+ 1.14.3
diff --git a/LICENSE b/LICENSE
index 1dc1bdb7411..ad4f2872db5 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2011-2016 GitLab B.V.
+Copyright (c) 2011-2017 GitLab B.V.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/PROCESS.md b/PROCESS.md
index 6eabaf05d24..fead93bd4cf 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -59,20 +59,38 @@ star, smile, etc.). Some good tips about code reviews can be found in our
## Feature Freeze
-On the 7th of each month, the stable branches for the upcoming release will
-be frozen for major changes. Merge requests may still be merged into master
-during this period. By freezing the stable branches prior to a release there's
-no need to worry about last minute merge requests potentially breaking a lot of
-things.
+After the 7th (Pacific Standard Time Zone) of each month, RC1 of the upcoming release is created and deployed to GitLab.com and the stable branch for this release is frozen, which means master is no longer merged into it.
+Merge requests may still be merged into master during this period,
+but they will go into the _next_ release, unless they are manually cherry-picked into the stable branch.
+By freezing the stable branches 2 weeks prior to a release, we reduce the risk of a last minute merge request potentially breaking things.
-What is considered to be a major change is determined on a case by case basis as
-this definition depends very much on the context of changes. For example, a 5
-line change might have a big impact on the entire application. Ultimately the
-decision will be made by the maintainers and the release managers.
+Once the stable branch is frozen, only fixes for regressions (bugs introduced in that same release)
+and security issues will be cherry-picked into the stable branch.
+Any merge requests cherry-picked into the stable branch for a previous release will also be picked into the latest stable branch.
+These fixes will be released in the next RC (before the 22nd) or patch release (after the 22nd).
+
+If you think a merge request should go into the upcoming release even though it does not meet these requirements,
+you can ask for an exception to be made. Exceptions require sign-off from 3 people besides the developer:
+
+1. a Release Manager
+2. an Engineering Lead
+3. an Engineering Director, the VP of Engineering, or the CTO
+
+You can find who is who on the [team page](https://about.gitlab.com/team/).
+
+Whether an exception is made is determined by weighing the benefit and urgency of the change
+(how important it is to the company that this is released _right now_ instead of in a month)
+against the potential negative impact
+(things breaking without enough time to comfortably find and fix them before the release on the 22nd).
+When in doubt, we err on the side of _not_ cherry-picking.
+
+For example, it is likely that an exception will be made for a trivial 1-5 line performance improvement
+(e.g. adding a database index or adding `includes` to a query), but not for a new feature, no matter how relatively small or thoroughly tested.
During the feature freeze all merge requests that are meant to go into the upcoming
release should have the correct milestone assigned _and_ have the label
-~"Pick into Stable" set. Merge requests without a milestone and this label will
+~"Pick into Stable" set, so that release managers can find and pick them.
+Merge requests without a milestone and this label will
not be merged into any stable branches.
## Copy & paste responses
diff --git a/README.md b/README.md
index 4f85fac4a56..09e08adbb73 100644
--- a/README.md
+++ b/README.md
@@ -29,7 +29,7 @@ We're hiring developers, support people, and production engineers all the time,
There are two editions of GitLab:
- GitLab Community Edition (CE) is available freely under the MIT Expat license.
-- GitLab Enterprise Edition (EE) includes [extra features](https://about.gitlab.com/features/#compare) that are more useful for organizations with more than 100 users. To use EE and get official support please [become a subscriber](https://about.gitlab.com/pricing/).
+- GitLab Enterprise Edition (EE) includes [extra features](https://about.gitlab.com/products/#compare-options) that are more useful for organizations with more than 100 users. To use EE and get official support please [become a subscriber](https://about.gitlab.com/products/).
## Website
diff --git a/VERSION b/VERSION
index 5c99c061a47..64de8316674 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-8.17.0-pre
+8.18.0-pre
diff --git a/app/assets/images/favicon-blue.ico b/app/assets/images/favicon-blue.ico
new file mode 100755
index 00000000000..156fcf07588
--- /dev/null
+++ b/app/assets/images/favicon-blue.ico
Binary files differ
diff --git a/app/assets/images/icon-merge-request-unmerged.svg b/app/assets/images/icon-merge-request-unmerged.svg
new file mode 100644
index 00000000000..c4d8e65122d
--- /dev/null
+++ b/app/assets/images/icon-merge-request-unmerged.svg
@@ -0,0 +1 @@
+<svg width="12" height="15" viewBox="0 0 12 15" xmlns="http://www.w3.org/2000/svg"><path d="M10.267 11.028V5.167c-.028-.728-.318-1.372-.878-1.923-.56-.55-1.194-.85-1.922-.877h-.934V.5l-2.8 2.8 2.8 2.8V4.233h.934a.976.976 0 0 1 .644.29.88.88 0 0 1 .289.644v5.861a1.86 1.86 0 0 0 .933 3.472 1.86 1.86 0 0 0 .934-3.472zM3.733 3.3a1.86 1.86 0 0 0-1.866-1.867 1.86 1.86 0 0 0-.934 3.472v6.123a1.86 1.86 0 0 0 .933 3.472 1.86 1.86 0 0 0 .934-3.472V4.905c.55-.317.933-.914.933-1.605z" fill-rule="nonzero"/></svg>
diff --git a/app/assets/images/mailers/gitlab_footer_logo.gif b/app/assets/images/mailers/gitlab_footer_logo.gif
new file mode 100644
index 00000000000..3f4ef31947b
--- /dev/null
+++ b/app/assets/images/mailers/gitlab_footer_logo.gif
Binary files differ
diff --git a/app/assets/images/mailers/gitlab_header_logo.gif b/app/assets/images/mailers/gitlab_header_logo.gif
new file mode 100644
index 00000000000..387628f831c
--- /dev/null
+++ b/app/assets/images/mailers/gitlab_header_logo.gif
Binary files differ
diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/admin.js
index 424dc719c78..aaed74d6073 100644
--- a/app/assets/javascripts/admin.js
+++ b/app/assets/javascripts/admin.js
@@ -61,4 +61,4 @@
return Admin;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/ajax_loading_spinner.js b/app/assets/javascripts/ajax_loading_spinner.js
new file mode 100644
index 00000000000..38a8317dbd7
--- /dev/null
+++ b/app/assets/javascripts/ajax_loading_spinner.js
@@ -0,0 +1,35 @@
+class AjaxLoadingSpinner {
+ static init() {
+ const $elements = $('.js-ajax-loading-spinner');
+
+ $elements.on('ajax:beforeSend', AjaxLoadingSpinner.ajaxBeforeSend);
+ $elements.on('ajax:complete', AjaxLoadingSpinner.ajaxComplete);
+ }
+
+ static ajaxBeforeSend(e) {
+ e.target.setAttribute('disabled', '');
+ const iconElement = e.target.querySelector('i');
+ // get first fa- icon
+ const originalIcon = iconElement.className.match(/(fa-)([^\s]+)/g).first();
+ iconElement.dataset.icon = originalIcon;
+ AjaxLoadingSpinner.toggleLoadingIcon(iconElement);
+ $(e.target).off('ajax:beforeSend', AjaxLoadingSpinner.ajaxBeforeSend);
+ }
+
+ static ajaxComplete(e) {
+ e.target.removeAttribute('disabled');
+ const iconElement = e.target.querySelector('i');
+ AjaxLoadingSpinner.toggleLoadingIcon(iconElement);
+ $(e.target).off('ajax:complete', AjaxLoadingSpinner.ajaxComplete);
+ }
+
+ static toggleLoadingIcon(iconElement) {
+ const classList = iconElement.classList;
+ classList.toggle(iconElement.dataset.icon);
+ classList.toggle('fa-spinner');
+ classList.toggle('fa-spin');
+ }
+}
+
+window.gl = window.gl || {};
+gl.AjaxLoadingSpinner = AjaxLoadingSpinner;
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index b4a8c827d7f..86e0ad89431 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -11,7 +11,7 @@
licensePath: "/api/:version/templates/licenses/:key",
gitignorePath: "/api/:version/templates/gitignores/:key",
gitlabCiYmlPath: "/api/:version/templates/gitlab_ci_ymls/:key",
- dockerfilePath: "/api/:version/dockerfiles/:key",
+ dockerfilePath: "/api/:version/templates/dockerfiles/:key",
issuableTemplatePath: "/:namespace_path/:project_path/templates/:type/:key",
group: function(group_id, callback) {
var url = Api.buildUrl(Api.groupPath)
@@ -147,4 +147,4 @@
};
window.Api = Api;
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index ea3f13bd00f..8e468faedbf 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -10,7 +10,6 @@ function requireAll(context) { return context.keys().map(context); }
window.$ = window.jQuery = require('jquery');
require('jquery-ui/ui/autocomplete');
-require('jquery-ui/ui/datepicker');
require('jquery-ui/ui/draggable');
require('jquery-ui/ui/effect-highlight');
require('jquery-ui/ui/sortable');
@@ -21,7 +20,7 @@ require('vendor/jquery.waitforimages');
require('vendor/jquery.caret');
require('vendor/jquery.atwho');
require('vendor/jquery.scrollTo');
-window.Cookies = require('vendor/js.cookie');
+window.Cookies = require('js-cookie');
require('./autosave');
require('bootstrap/js/affix');
require('bootstrap/js/alert');
@@ -35,8 +34,10 @@ require('bootstrap/js/transition');
require('bootstrap/js/tooltip');
require('bootstrap/js/popover');
require('select2/select2.js');
+window.Pikaday = require('pikaday');
window._ = require('underscore');
window.Dropzone = require('dropzone');
+window.Sortable = require('vendor/Sortable');
require('mousetrap');
require('mousetrap/plugins/pause/mousetrap-pause');
require('./shortcuts');
@@ -55,8 +56,7 @@ requireAll(require.context('./u2f', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('./droplab', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('.', false, /^\.\/(?!application\.js).*\.(js|es6)$/));
require('vendor/fuzzaldrin-plus');
-window.ES6Promise = require('vendor/es6-promise.auto');
-window.ES6Promise.polyfill();
+require('es6-promise').polyfill();
(function () {
document.addEventListener('beforeunload', function () {
@@ -101,11 +101,6 @@ window.ES6Promise.polyfill();
}
});
- $('.nav-sidebar').niceScroll({
- cursoropacitymax: '0.4',
- cursorcolor: '#FFF',
- cursorborder: '1px solid #FFF'
- });
$('.js-select-on-focus').on('focusin', function () {
return $(this).select().one('mouseup', function (e) {
return e.preventDefault();
@@ -245,9 +240,7 @@ window.ES6Promise.polyfill();
});
gl.awardsHandler = new AwardsHandler();
new Aside();
- // bind sidebar events
- new gl.Sidebar();
gl.utils.initTimeagoTimeout();
});
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/aside.js b/app/assets/javascripts/aside.js
index 8438de6cdf1..448e6e2cc78 100644
--- a/app/assets/javascripts/aside.js
+++ b/app/assets/javascripts/aside.js
@@ -22,4 +22,4 @@
return Aside;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/autosave.js b/app/assets/javascripts/autosave.js
index b16a2c0f73a..e55405135fb 100644
--- a/app/assets/javascripts/autosave.js
+++ b/app/assets/javascripts/autosave.js
@@ -59,4 +59,4 @@
return Autosave;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index 9d776b74965..a4ccb30e447 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -377,4 +377,4 @@ var emojiAliases = require('emoji-aliases');
return AwardsHandler;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/behaviors/autosize.js b/app/assets/javascripts/behaviors/autosize.js
index a489523b802..f7f41d55b52 100644
--- a/app/assets/javascripts/behaviors/autosize.js
+++ b/app/assets/javascripts/behaviors/autosize.js
@@ -25,4 +25,4 @@ var autosize = require('vendor/autosize');
autosize.update($fields);
return $fields.css('resize', 'vertical');
});
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/behaviors/details_behavior.js b/app/assets/javascripts/behaviors/details_behavior.js
index 6af8f593872..fd0840fa117 100644
--- a/app/assets/javascripts/behaviors/details_behavior.js
+++ b/app/assets/javascripts/behaviors/details_behavior.js
@@ -23,4 +23,4 @@
return e.preventDefault();
});
});
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/behaviors/quick_submit.js b/app/assets/javascripts/behaviors/quick_submit.js
index 7747306688c..a7e68ae5cb9 100644
--- a/app/assets/javascripts/behaviors/quick_submit.js
+++ b/app/assets/javascripts/behaviors/quick_submit.js
@@ -74,4 +74,4 @@ require('../extensions/jquery');
return $this.tooltip('hide');
});
});
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/behaviors/requires_input.js b/app/assets/javascripts/behaviors/requires_input.js
index 6276933e93e..6b21695d082 100644
--- a/app/assets/javascripts/behaviors/requires_input.js
+++ b/app/assets/javascripts/behaviors/requires_input.js
@@ -59,4 +59,4 @@ require('../extensions/jquery');
return hideOrShowHelpBlock($form);
});
});
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js
index 04bfe363929..5f14ff40eee 100644
--- a/app/assets/javascripts/blob/blob_file_dropzone.js
+++ b/app/assets/javascripts/blob/blob_file_dropzone.js
@@ -63,4 +63,4 @@
return BlobFileDropzone;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/blob/blob_gitignore_selector.js b/app/assets/javascripts/blob/blob_gitignore_selector.js
index 1d0bcf6471f..de20eab9cd1 100644
--- a/app/assets/javascripts/blob/blob_gitignore_selector.js
+++ b/app/assets/javascripts/blob/blob_gitignore_selector.js
@@ -20,4 +20,4 @@ require('./template_selector');
return BlobGitignoreSelector;
})(gl.TemplateSelector);
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/blob/blob_gitignore_selectors.js b/app/assets/javascripts/blob/blob_gitignore_selectors.js
index 8236457f0f1..43e5c0a5641 100644
--- a/app/assets/javascripts/blob/blob_gitignore_selectors.js
+++ b/app/assets/javascripts/blob/blob_gitignore_selectors.js
@@ -23,4 +23,4 @@
return BlobGitignoreSelectors;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/blob/blob_license_selector.js b/app/assets/javascripts/blob/blob_license_selector.js
index 1d5672d4c48..b582052a76e 100644
--- a/app/assets/javascripts/blob/blob_license_selector.js
+++ b/app/assets/javascripts/blob/blob_license_selector.js
@@ -25,4 +25,4 @@ require('./template_selector');
return BlobLicenseSelector;
})(gl.TemplateSelector);
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/blob_edit/blob_edit_bundle.js b/app/assets/javascripts/blob_edit/blob_edit_bundle.js
index 9e0754819fa..0436bbb0eaf 100644
--- a/app/assets/javascripts/blob_edit/blob_edit_bundle.js
+++ b/app/assets/javascripts/blob_edit/blob_edit_bundle.js
@@ -12,4 +12,4 @@ require('./edit_blob');
var blob = new EditBlob(url, $('.js-edit-blob-form').data('blob-language'));
new NewCommitForm($('.js-edit-blob-form'));
});
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js
index 079445e8278..a1127b9e30e 100644
--- a/app/assets/javascripts/blob_edit/edit_blob.js
+++ b/app/assets/javascripts/blob_edit/edit_blob.js
@@ -85,4 +85,4 @@
return EditBlob;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/boards/boards_bundle.js.es6 b/app/assets/javascripts/boards/boards_bundle.js.es6
index c345fb6ce14..55d13be6e5f 100644
--- a/app/assets/javascripts/boards/boards_bundle.js.es6
+++ b/app/assets/javascripts/boards/boards_bundle.js.es6
@@ -1,17 +1,20 @@
-/* eslint-disable one-var, quote-props, comma-dangle, space-before-function-paren, import/newline-after-import, no-multi-spaces, max-len */
+/* eslint-disable one-var, quote-props, comma-dangle, space-before-function-paren */
/* global Vue */
/* global BoardService */
-function requireAll(context) { return context.keys().map(context); }
-
window.Vue = require('vue');
window.Vue.use(require('vue-resource'));
-window.Sortable = require('vendor/Sortable');
-requireAll(require.context('./models', true, /^\.\/.*\.(js|es6)$/));
-requireAll(require.context('./stores', true, /^\.\/.*\.(js|es6)$/));
-requireAll(require.context('./services', true, /^\.\/.*\.(js|es6)$/));
-requireAll(require.context('./mixins', true, /^\.\/.*\.(js|es6)$/));
-requireAll(require.context('./filters', true, /^\.\/.*\.(js|es6)$/));
+require('./models/issue');
+require('./models/label');
+require('./models/list');
+require('./models/milestone');
+require('./models/user');
+require('./stores/boards_store');
+require('./stores/modal_store');
+require('./services/board_service');
+require('./mixins/modal_mixins');
+require('./mixins/sortable_default_options');
+require('./filters/due_date_filters');
require('./components/board');
require('./components/board_sidebar');
require('./components/new_list_dropdown');
@@ -94,17 +97,53 @@ $(() => {
modal: ModalStore.store,
store: Store.state,
},
+ watch: {
+ disabled() {
+ this.updateTooltip();
+ },
+ },
computed: {
disabled() {
- return Store.shouldAddBlankState();
+ return !this.store.lists.filter(list => list.type !== 'blank' && list.type !== 'done').length;
+ },
+ tooltipTitle() {
+ if (this.disabled) {
+ return 'Please add a list to your board first';
+ }
+
+ return '';
},
},
+ methods: {
+ updateTooltip() {
+ const $tooltip = $(this.$el);
+
+ this.$nextTick(() => {
+ if (this.disabled) {
+ $tooltip.tooltip();
+ } else {
+ $tooltip.tooltip('destroy');
+ }
+ });
+ },
+ openModal() {
+ if (!this.disabled) {
+ this.toggleModal(true);
+ }
+ },
+ },
+ mounted() {
+ this.updateTooltip();
+ },
template: `
<button
- class="btn btn-create pull-right prepend-left-10 has-tooltip"
+ class="btn btn-create pull-right prepend-left-10"
type="button"
- :disabled="disabled"
- @click="toggleModal(true)">
+ data-placement="bottom"
+ :class="{ 'disabled': disabled }"
+ :title="tooltipTitle"
+ :aria-disabled="disabled"
+ @click="openModal">
Add issues
</button>
`,
diff --git a/app/assets/javascripts/boards/filters/due_date_filters.js.es6 b/app/assets/javascripts/boards/filters/due_date_filters.js.es6
index 7e192e90fe6..03425bb145b 100644
--- a/app/assets/javascripts/boards/filters/due_date_filters.js.es6
+++ b/app/assets/javascripts/boards/filters/due_date_filters.js.es6
@@ -1,6 +1,7 @@
/* global Vue */
+/* global dateFormat */
Vue.filter('due-date', (value) => {
const date = new Date(value);
- return $.datepicker.formatDate('M d, yy', date);
+ return dateFormat(date, 'mmm d, yyyy', true);
});
diff --git a/app/assets/javascripts/breakpoints.js b/app/assets/javascripts/breakpoints.js
index f8dac1ff56e..22e93328548 100644
--- a/app/assets/javascripts/breakpoints.js
+++ b/app/assets/javascripts/breakpoints.js
@@ -69,4 +69,4 @@
})(this));
window.Breakpoints = Breakpoints;
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/broadcast_message.js b/app/assets/javascripts/broadcast_message.js
index dbdadc73c3f..e8531c43b4b 100644
--- a/app/assets/javascripts/broadcast_message.js
+++ b/app/assets/javascripts/broadcast_message.js
@@ -31,4 +31,4 @@
}
});
});
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js
index 0152be88b48..8fa1aceddff 100644
--- a/app/assets/javascripts/build.js
+++ b/app/assets/javascripts/build.js
@@ -67,16 +67,8 @@
Build.prototype.initSidebar = function() {
this.$sidebar = $('.js-build-sidebar');
- this.sidebarTranslationLimits = {
- min: $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight()
- };
- this.sidebarTranslationLimits.max = this.sidebarTranslationLimits.min + $('.scrolling-tabs-container').outerHeight();
- this.$sidebar.css({
- top: this.sidebarTranslationLimits.max
- });
this.$sidebar.niceScroll();
this.$document.off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar);
- this.$document.off('scroll.translateSidebar').on('scroll.translateSidebar', this.translateSidebar.bind(this));
};
Build.prototype.location = function() {
@@ -231,14 +223,6 @@
return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
};
- Build.prototype.translateSidebar = function(e) {
- var newPosition = this.sidebarTranslationLimits.max - (document.body.scrollTop || document.documentElement.scrollTop);
- if (newPosition < this.sidebarTranslationLimits.min) newPosition = this.sidebarTranslationLimits.min;
- this.$sidebar.css({
- top: newPosition
- });
- };
-
Build.prototype.toggleSidebar = function(shouldHide) {
var shouldShow = typeof shouldHide === 'boolean' ? !shouldHide : undefined;
this.$buildScroll.toggleClass('sidebar-expanded', shouldShow)
@@ -285,10 +269,10 @@
e.preventDefault();
$currentTarget = $(e.currentTarget);
$.scrollTo($currentTarget.attr('href'), {
- offset: -($('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight())
+ offset: 0
});
};
return Build;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/build_artifacts.js b/app/assets/javascripts/build_artifacts.js
index 083448552b6..cae9a0ffca4 100644
--- a/app/assets/javascripts/build_artifacts.js
+++ b/app/assets/javascripts/build_artifacts.js
@@ -23,4 +23,4 @@
return BuildArtifacts;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/commit.js b/app/assets/javascripts/commit.js
index c656ae4e241..566b322eb49 100644
--- a/app/assets/javascripts/commit.js
+++ b/app/assets/javascripts/commit.js
@@ -11,4 +11,4 @@
return Commit;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/commit/file.js b/app/assets/javascripts/commit/file.js
index 184b4561d2e..ee087c978dd 100644
--- a/app/assets/javascripts/commit/file.js
+++ b/app/assets/javascripts/commit/file.js
@@ -11,4 +11,4 @@
return CommitFile;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js
index f09a6b1e676..49bb64a3472 100644
--- a/app/assets/javascripts/commit/image_file.js
+++ b/app/assets/javascripts/commit/image_file.js
@@ -173,4 +173,4 @@
return ImageFile;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6
index fbfec7743c7..b5a988df897 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6
+++ b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6
@@ -20,7 +20,10 @@ $(() => {
gl.commits.PipelinesTableBundle.$destroy(true);
}
- gl.commits.pipelines.PipelinesTableBundle = new gl.commits.pipelines.PipelinesTableView({
- el: document.querySelector('#commit-pipeline-table-view'),
- });
+ const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
+ gl.commits.pipelines.PipelinesTableBundle = new gl.commits.pipelines.PipelinesTableView();
+
+ if (pipelineTableViewEl && pipelineTableViewEl.dataset.disableInitialization === undefined) {
+ gl.commits.pipelines.PipelinesTableBundle.$mount(pipelineTableViewEl);
+ }
});
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_service.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_service.js.es6
index 483b414126a..8ae98f9bf97 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_service.js.es6
+++ b/app/assets/javascripts/commit/pipelines/pipelines_service.js.es6
@@ -8,7 +8,22 @@
* Uses Vue.Resource
*/
class PipelinesService {
- constructor(endpoint) {
+
+ /**
+ * FIXME: The url provided to request the pipelines in the new merge request
+ * page already has `.json`.
+ * This should be fixed when the endpoint is improved.
+ *
+ * @param {String} root
+ */
+ constructor(root) {
+ let endpoint;
+
+ if (root.indexOf('.json') === -1) {
+ endpoint = `${root}.json`;
+ } else {
+ endpoint = root;
+ }
this.pipelines = Vue.resource(endpoint);
}
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_store.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_store.js.es6
index 11a3449d99a..f1b80e45444 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_store.js.es6
+++ b/app/assets/javascripts/commit/pipelines/pipelines_store.js.es6
@@ -4,6 +4,7 @@
*
* Used to store the Pipelines rendered in the commit view in the pipelines table.
*/
+require('../../vue_realtime_listener');
class PipelinesStore {
constructor() {
@@ -24,9 +25,9 @@ class PipelinesStore {
* update the time to show how long as passed.
*
*/
- startTimeAgoLoops() {
+ static startTimeAgoLoops() {
const startTimeLoops = () => {
- this.timeLoopInterval = setInterval(function timeloopInterval() {
+ this.timeLoopInterval = setInterval(() => {
this.$children[0].$children.reduce((acc, component) => {
const timeAgoComponent = component.$children.filter(el => el.$options._componentTag === 'time-ago')[0];
acc.push(timeAgoComponent);
@@ -44,7 +45,4 @@ class PipelinesStore {
}
}
-window.gl = window.gl || {};
-gl.commits = gl.commits || {};
-gl.commits.pipelines = gl.commits.pipelines || {};
-gl.commits.pipelines.PipelinesStore = PipelinesStore;
+module.exports = PipelinesStore;
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_table.js.es6
index ce0dbd4d56b..e7c6c063413 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_table.js.es6
+++ b/app/assets/javascripts/commit/pipelines/pipelines_table.js.es6
@@ -6,9 +6,8 @@ window.Vue.use(require('vue-resource'));
require('../../lib/utils/common_utils');
require('../../vue_shared/vue_resource_interceptor');
require('../../vue_shared/components/pipelines_table');
-require('../../vue_realtime_listener/index');
require('./pipelines_service');
-require('./pipelines_store');
+const PipelineStore = require('./pipelines_store');
/**
*
@@ -41,7 +40,7 @@ require('./pipelines_store');
data() {
const pipelinesTableData = document.querySelector('#commit-pipeline-table-view').dataset;
const svgsData = document.querySelector('.pipeline-svgs').dataset;
- const store = new gl.commits.pipelines.PipelinesStore();
+ const store = new PipelineStore();
// Transform svgs DOMStringMap to a plain Object.
const svgsObject = gl.utils.DOMStringMapToObject(svgsData);
@@ -56,15 +55,14 @@ require('./pipelines_store');
},
/**
- * When the component is created the service to fetch the data will be
- * initialized with the correct endpoint.
+ * When the component is about to be mounted, tell the service to fetch the data
*
* A request to fetch the pipelines will be made.
* In case of a successfull response we will store the data in the provided
* store, in case of a failed response we need to warn the user.
*
*/
- created() {
+ beforeMount() {
const pipelinesService = new gl.commits.pipelines.PipelinesService(this.endpoint);
this.isLoading = true;
@@ -72,7 +70,6 @@ require('./pipelines_store');
.then(response => response.json())
.then((json) => {
this.store.storePipelines(json);
- this.store.startTimeAgoLoops.call(this, Vue);
this.isLoading = false;
})
.catch(() => {
@@ -81,9 +78,15 @@ require('./pipelines_store');
});
},
+ beforeUpdate() {
+ if (this.state.pipelines.length && this.$children) {
+ PipelineStore.startTimeAgoLoops.call(this, Vue);
+ }
+ },
+
template: `
- <div>
- <div class="pipelines realtime-loading" v-if="isLoading">
+ <div class="pipelines">
+ <div class="realtime-loading" v-if="isLoading">
<i class="fa fa-spinner fa-spin"></i>
</div>
diff --git a/app/assets/javascripts/commits.js b/app/assets/javascripts/commits.js
index c6fdfbcaa10..ccd895f3bf4 100644
--- a/app/assets/javascripts/commits.js
+++ b/app/assets/javascripts/commits.js
@@ -65,4 +65,4 @@
return CommitsList;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/compare.js b/app/assets/javascripts/compare.js
index 9591df70e9c..15df105d4cc 100644
--- a/app/assets/javascripts/compare.js
+++ b/app/assets/javascripts/compare.js
@@ -88,4 +88,4 @@
return Compare;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/compare_autocomplete.js.es6 b/app/assets/javascripts/compare_autocomplete.js.es6
index 3587431ab69..1eca973e069 100644
--- a/app/assets/javascripts/compare_autocomplete.js.es6
+++ b/app/assets/javascripts/compare_autocomplete.js.es6
@@ -66,4 +66,4 @@
return CompareAutocomplete;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/confirm_danger_modal.js b/app/assets/javascripts/confirm_danger_modal.js
index 35d98492012..a1c1b721228 100644
--- a/app/assets/javascripts/confirm_danger_modal.js
+++ b/app/assets/javascripts/confirm_danger_modal.js
@@ -28,4 +28,4 @@
return ConfirmDangerModal;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/copy_as_gfm.js.es6 b/app/assets/javascripts/copy_as_gfm.js.es6
index 2bfe57b4100..4bd537a6f28 100644
--- a/app/assets/javascripts/copy_as_gfm.js.es6
+++ b/app/assets/javascripts/copy_as_gfm.js.es6
@@ -91,6 +91,9 @@ require('./lib/utils/common_utils');
},
},
SanitizationFilter: {
+ 'a[name]:not([href]):empty'(el, text) {
+ return el.outerHTML;
+ },
'dl'(el, text) {
let lines = text.trim().split('\n');
// Add two spaces to the front of subsequent list items lines,
diff --git a/app/assets/javascripts/copy_to_clipboard.js b/app/assets/javascripts/copy_to_clipboard.js
index 0029c59e550..615f485e18a 100644
--- a/app/assets/javascripts/copy_to_clipboard.js
+++ b/app/assets/javascripts/copy_to_clipboard.js
@@ -46,4 +46,4 @@ window.Clipboard = require('vendor/clipboard');
clipboard.on('success', genericSuccess);
return clipboard.on('error', genericError);
});
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/create_label.js.es6 b/app/assets/javascripts/create_label.js.es6
index 947c129d5b5..85384d98126 100644
--- a/app/assets/javascripts/create_label.js.es6
+++ b/app/assets/javascripts/create_label.js.es6
@@ -107,9 +107,9 @@
if (typeof label.message === 'string') {
errors = label.message;
} else {
- errors = label.message.map(function (value, key) {
- return key + " " + value[0];
- }).join("<br/>");
+ errors = Object.keys(label.message).map(key =>
+ `${gl.text.humanize(key)} ${label.message[key].join(', ')}`
+ ).join("<br/>");
}
this.$newLabelError
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js.es6 b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js.es6
index 513298ba4e7..8652479e7bf 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js.es6
+++ b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js.es6
@@ -13,6 +13,12 @@
<div>
<div class="events-description">
{{ stage.description }}
+ <span v-if="items.length === 50" class="events-info pull-right">
+ <i class="fa fa-warning has-tooltip"
+ title="Limited to showing 50 events at most"
+ data-placement="top"></i>
+ Showing 50 events
+ </span>
</div>
<ul class="stage-event-list">
<li v-for="commit in items" class="stage-event-item">
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js.es6 b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js.es6
index c41c57c1dcd..411ac7b24b2 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js.es6
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js.es6
@@ -3,11 +3,21 @@
/* global Flash */
window.Vue = require('vue');
-window.Cookies = require('vendor/js.cookie');
-
-function requireAll(context) { return context.keys().map(context); }
-requireAll(require.context('./svg', false, /^\.\/.*\.(js|es6)$/));
-requireAll(require.context('.', true, /^\.\/(?!cycle_analytics_bundle).*\.(js|es6)$/));
+window.Cookies = require('js-cookie');
+require('./svg/icon_branch');
+require('./svg/icon_build_status');
+require('./svg/icon_commit');
+require('./components/stage_code_component');
+require('./components/stage_issue_component');
+require('./components/stage_plan_component');
+require('./components/stage_production_component');
+require('./components/stage_review_component');
+require('./components/stage_staging_component');
+require('./components/stage_test_component');
+require('./components/total_time_component');
+require('./cycle_analytics_service');
+require('./cycle_analytics_store');
+require('./default_event_objects');
$(() => {
const OVERVIEW_DIALOG_COOKIE = 'cycle_analytics_help_dismissed';
@@ -97,7 +107,7 @@ $(() => {
}
this.isLoadingStage = true;
- cycleAnalyticsStore.setStageEvents([]);
+ cycleAnalyticsStore.setStageEvents([], stage);
cycleAnalyticsStore.setActiveStage(stage);
cycleAnalyticsService
@@ -107,7 +117,7 @@ $(() => {
})
.done((response) => {
this.isEmptyStage = !response.events.length;
- cycleAnalyticsStore.setStageEvents(response.events);
+ cycleAnalyticsStore.setStageEvents(response.events, stage);
})
.error(() => {
this.isEmptyStage = true;
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js.es6 b/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js.es6
index be732971c7f..3efeb141008 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js.es6
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js.es6
@@ -1,4 +1,8 @@
/* eslint-disable no-param-reassign */
+
+require('../lib/utils/text_utility');
+const DEFAULT_EVENT_OBJECTS = require('./default_event_objects');
+
((global) => {
global.cycleAnalytics = global.cycleAnalytics || {};
@@ -34,11 +38,12 @@
});
newData.stages.forEach((item) => {
- const stageName = item.title.toLowerCase();
+ const stageSlug = gl.text.dasherize(item.title.toLowerCase());
item.active = false;
- item.isUserAllowed = data.permissions[stageName];
- item.emptyStageText = EMPTY_STAGE_TEXTS[stageName];
- item.component = `stage-${stageName}-component`;
+ item.isUserAllowed = data.permissions[stageSlug];
+ item.emptyStageText = EMPTY_STAGE_TEXTS[stageSlug];
+ item.component = `stage-${stageSlug}-component`;
+ item.slug = stageSlug;
});
newData.analytics = data;
return newData;
@@ -58,31 +63,33 @@
this.deactivateAllStages();
stage.active = true;
},
- setStageEvents(events) {
- this.state.events = this.decorateEvents(events);
+ setStageEvents(events, stage) {
+ this.state.events = this.decorateEvents(events, stage);
},
- decorateEvents(events) {
+ decorateEvents(events, stage) {
const newEvents = [];
events.forEach((item) => {
if (!item) return;
- item.totalTime = item.total_time;
- item.author.webUrl = item.author.web_url;
- item.author.avatarUrl = item.author.avatar_url;
+ const eventItem = Object.assign({}, DEFAULT_EVENT_OBJECTS[stage.slug], item);
+
+ eventItem.totalTime = eventItem.total_time;
+ eventItem.author.webUrl = eventItem.author.web_url;
+ eventItem.author.avatarUrl = eventItem.author.avatar_url;
- if (item.created_at) item.createdAt = item.created_at;
- if (item.short_sha) item.shortSha = item.short_sha;
- if (item.commit_url) item.commitUrl = item.commit_url;
+ if (eventItem.created_at) eventItem.createdAt = eventItem.created_at;
+ if (eventItem.short_sha) eventItem.shortSha = eventItem.short_sha;
+ if (eventItem.commit_url) eventItem.commitUrl = eventItem.commit_url;
- delete item.author.web_url;
- delete item.author.avatar_url;
- delete item.total_time;
- delete item.created_at;
- delete item.short_sha;
- delete item.commit_url;
+ delete eventItem.author.web_url;
+ delete eventItem.author.avatar_url;
+ delete eventItem.total_time;
+ delete eventItem.created_at;
+ delete eventItem.short_sha;
+ delete eventItem.commit_url;
- newEvents.push(item);
+ newEvents.push(eventItem);
});
return newEvents;
diff --git a/app/assets/javascripts/cycle_analytics/default_event_objects.js.es6 b/app/assets/javascripts/cycle_analytics/default_event_objects.js.es6
new file mode 100644
index 00000000000..cfaf9835bf8
--- /dev/null
+++ b/app/assets/javascripts/cycle_analytics/default_event_objects.js.es6
@@ -0,0 +1,98 @@
+module.exports = {
+ issue: {
+ created_at: '',
+ url: '',
+ iid: '',
+ title: '',
+ total_time: {},
+ author: {
+ avatar_url: '',
+ id: '',
+ name: '',
+ web_url: '',
+ },
+ },
+ plan: {
+ title: '',
+ commit_url: '',
+ short_sha: '',
+ total_time: {},
+ author: {
+ name: '',
+ id: '',
+ avatar_url: '',
+ web_url: '',
+ },
+ },
+ code: {
+ title: '',
+ iid: '',
+ created_at: '',
+ url: '',
+ total_time: {},
+ author: {
+ name: '',
+ id: '',
+ avatar_url: '',
+ web_url: '',
+ },
+ },
+ test: {
+ name: '',
+ id: '',
+ date: '',
+ url: '',
+ short_sha: '',
+ commit_url: '',
+ total_time: {},
+ branch: {
+ name: '',
+ url: '',
+ },
+ },
+ review: {
+ title: '',
+ iid: '',
+ created_at: '',
+ url: '',
+ state: '',
+ total_time: {},
+ author: {
+ name: '',
+ id: '',
+ avatar_url: '',
+ web_url: '',
+ },
+ },
+ staging: {
+ id: '',
+ short_sha: '',
+ date: '',
+ url: '',
+ commit_url: '',
+ total_time: {},
+ author: {
+ name: '',
+ id: '',
+ avatar_url: '',
+ web_url: '',
+ },
+ branch: {
+ name: '',
+ url: '',
+ },
+ },
+ production: {
+ title: '',
+ created_at: '',
+ url: '',
+ iid: '',
+ total_time: {},
+ author: {
+ name: '',
+ id: '',
+ avatar_url: '',
+ web_url: '',
+ },
+ },
+};
diff --git a/app/assets/javascripts/diff.js.es6 b/app/assets/javascripts/diff.js.es6
index c39e30fb7e0..ccccd0a36ff 100644
--- a/app/assets/javascripts/diff.js.es6
+++ b/app/assets/javascripts/diff.js.es6
@@ -76,7 +76,7 @@ require('./lib/utils/url_utility');
const diffFile = diffTitle.closest('.diff-file');
const nothingHereBlock = $('.nothing-here-block:visible', diffFile);
if (nothingHereBlock.length) {
- const clickTarget = $('.file-title, .click-to-expand', diffFile);
+ const clickTarget = $('.js-file-title, .click-to-expand', diffFile);
diffFile.data('singleFileDiff').toggleDiff(clickTarget, () => {
this.highlighSelectedLine();
if (cb) cb();
diff --git a/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js.es6 b/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js.es6
index 2514459e65e..d948dff58ec 100644
--- a/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js.es6
+++ b/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js.es6
@@ -1,6 +1,6 @@
/* eslint-disable comma-dangle, object-shorthand, func-names, no-else-return, quotes, no-lonely-if, max-len */
-/* global Vue */
/* global CommentsStore */
+const Vue = require('vue');
(() => {
const CommentAndResolveBtn = Vue.extend({
@@ -9,13 +9,11 @@
},
data() {
return {
- textareaIsEmpty: true
+ textareaIsEmpty: true,
+ discussion: {},
};
},
computed: {
- discussion: function () {
- return CommentsStore.state[this.discussionId];
- },
showButton: function () {
if (this.discussion) {
return this.discussion.isResolvable();
@@ -42,6 +40,9 @@
}
}
},
+ created() {
+ this.discussion = CommentsStore.state[this.discussionId];
+ },
mounted: function () {
const $textarea = $(`#new-discussion-note-form-${this.discussionId} .note-textarea`);
this.textareaIsEmpty = $textarea.val() === '';
diff --git a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js.es6 b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js.es6
index c3898873eaa..283dc330cad 100644
--- a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js.es6
+++ b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js.es6
@@ -1,7 +1,7 @@
/* eslint-disable comma-dangle, object-shorthand, func-names, no-else-return, guard-for-in, no-restricted-syntax, one-var, space-before-function-paren, no-lonely-if, no-continue, brace-style, max-len, quotes */
-/* global Vue */
/* global DiscussionMixins */
/* global CommentsStore */
+const Vue = require('vue');
(() => {
const JumpToDiscussion = Vue.extend({
@@ -12,12 +12,10 @@
data: function () {
return {
discussions: CommentsStore.state,
+ discussion: {},
};
},
computed: {
- discussion: function () {
- return this.discussions[this.discussionId];
- },
allResolved: function () {
return this.unresolvedDiscussionCount === 0;
},
@@ -183,10 +181,13 @@
}
$.scrollTo($target, {
- offset: -($('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight())
+ offset: 0
});
}
- }
+ },
+ created() {
+ this.discussion = this.discussions[this.discussionId];
+ },
});
Vue.component('jump-to-discussion', JumpToDiscussion);
diff --git a/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6 b/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6
index 5852b8bbdb7..d1873d6c7a2 100644
--- a/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6
+++ b/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6
@@ -1,8 +1,8 @@
/* eslint-disable comma-dangle, object-shorthand, func-names, quote-props, no-else-return, camelcase, no-new, max-len */
-/* global Vue */
/* global CommentsStore */
/* global ResolveService */
/* global Flash */
+const Vue = require('vue');
(() => {
const ResolveBtn = Vue.extend({
@@ -10,14 +10,14 @@
noteId: Number,
discussionId: String,
resolved: Boolean,
- projectPath: String,
canResolve: Boolean,
resolvedBy: String
},
data: function () {
return {
discussions: CommentsStore.state,
- loading: false
+ loading: false,
+ note: {},
};
},
watch: {
@@ -30,13 +30,6 @@
discussion: function () {
return this.discussions[this.discussionId];
},
- note: function () {
- if (this.discussion) {
- return this.discussion.getNote(this.noteId);
- } else {
- return undefined;
- }
- },
buttonText: function () {
if (this.isResolved) {
return `Resolved by ${this.resolvedByName}`;
@@ -73,10 +66,10 @@
if (this.isResolved) {
promise = ResolveService
- .unresolve(this.projectPath, this.noteId);
+ .unresolve(this.noteId);
} else {
promise = ResolveService
- .resolve(this.projectPath, this.noteId);
+ .resolve(this.noteId);
}
promise.then((response) => {
@@ -106,6 +99,8 @@
},
created: function () {
CommentsStore.create(this.discussionId, this.noteId, this.canResolve, this.resolved, this.resolvedBy);
+
+ this.note = this.discussion.getNote(this.noteId);
}
});
diff --git a/app/assets/javascripts/diff_notes/components/resolve_count.js.es6 b/app/assets/javascripts/diff_notes/components/resolve_count.js.es6
index 72cdae812bc..de9367f2136 100644
--- a/app/assets/javascripts/diff_notes/components/resolve_count.js.es6
+++ b/app/assets/javascripts/diff_notes/components/resolve_count.js.es6
@@ -1,7 +1,7 @@
/* eslint-disable comma-dangle, object-shorthand, func-names, no-param-reassign */
-/* global Vue */
/* global DiscussionMixins */
/* global CommentsStore */
+const Vue = require('vue');
((w) => {
w.ResolveCount = Vue.extend({
diff --git a/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js.es6 b/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js.es6
index ee5f62b2d9e..7c5fcd04d2d 100644
--- a/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js.es6
+++ b/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js.es6
@@ -1,25 +1,22 @@
/* eslint-disable object-shorthand, func-names, space-before-function-paren, comma-dangle, no-else-return, quotes, max-len */
-/* global Vue */
/* global CommentsStore */
/* global ResolveService */
+const Vue = require('vue');
+
(() => {
const ResolveDiscussionBtn = Vue.extend({
props: {
discussionId: String,
mergeRequestId: Number,
- projectPath: String,
canResolve: Boolean,
},
data: function() {
return {
- discussions: CommentsStore.state
+ discussion: {},
};
},
computed: {
- discussion: function () {
- return this.discussions[this.discussionId];
- },
showButton: function () {
if (this.discussion) {
return this.discussion.isResolvable();
@@ -51,11 +48,13 @@
},
methods: {
resolve: function () {
- ResolveService.toggleResolveForDiscussion(this.projectPath, this.mergeRequestId, this.discussionId);
+ ResolveService.toggleResolveForDiscussion(this.mergeRequestId, this.discussionId);
}
},
created: function () {
CommentsStore.createDiscussion(this.discussionId, this.canResolve);
+
+ this.discussion = CommentsStore.state[this.discussionId];
}
});
diff --git a/app/assets/javascripts/diff_notes/diff_notes_bundle.js.es6 b/app/assets/javascripts/diff_notes/diff_notes_bundle.js.es6
index f0edfb8aaf1..cadf8b96b87 100644
--- a/app/assets/javascripts/diff_notes/diff_notes_bundle.js.es6
+++ b/app/assets/javascripts/diff_notes/diff_notes_bundle.js.es6
@@ -1,20 +1,28 @@
-/* eslint-disable func-names, comma-dangle, new-cap, no-new, import/newline-after-import, no-multi-spaces, max-len */
+/* eslint-disable func-names, comma-dangle, new-cap, no-new, max-len */
/* global Vue */
/* global ResolveCount */
-function requireAll(context) { return context.keys().map(context); }
-requireAll(require.context('./models', false, /^\.\/.*\.(js|es6)$/));
-requireAll(require.context('./stores', false, /^\.\/.*\.(js|es6)$/));
-requireAll(require.context('./services', false, /^\.\/.*\.(js|es6)$/));
-requireAll(require.context('./mixins', false, /^\.\/.*\.(js|es6)$/));
-requireAll(require.context('./components', false, /^\.\/.*\.(js|es6)$/));
+const Vue = require('vue');
+require('./models/discussion');
+require('./models/note');
+require('./stores/comments');
+require('./services/resolve');
+require('./mixins/discussion');
+require('./components/comment_resolve_btn');
+require('./components/jump_to_discussion');
+require('./components/resolve_btn');
+require('./components/resolve_count');
+require('./components/resolve_discussion_btn');
$(() => {
+ const projectPath = document.querySelector('.merge-request').dataset.projectPath;
const COMPONENT_SELECTOR = 'resolve-btn, resolve-discussion-btn, jump-to-discussion, comment-and-resolve-btn';
window.gl = window.gl || {};
window.gl.diffNoteApps = {};
+ window.ResolveService = new gl.DiffNotesResolveServiceClass(projectPath);
+
gl.diffNotesCompileComponents = () => {
const $components = $(COMPONENT_SELECTOR).filter(function () {
return $(this).closest('resolve-count').length !== 1;
diff --git a/app/assets/javascripts/diff_notes/services/resolve.js.es6 b/app/assets/javascripts/diff_notes/services/resolve.js.es6
index a52c476352d..090c454e9e4 100644
--- a/app/assets/javascripts/diff_notes/services/resolve.js.es6
+++ b/app/assets/javascripts/diff_notes/services/resolve.js.es6
@@ -1,45 +1,37 @@
/* eslint-disable class-methods-use-this, one-var, camelcase, no-new, comma-dangle, no-param-reassign, max-len */
-/* global Vue */
/* global Flash */
/* global CommentsStore */
-((w) => {
- class ResolveServiceClass {
- constructor() {
- this.noteResource = Vue.resource('notes{/noteId}/resolve');
- this.discussionResource = Vue.resource('merge_requests{/mergeRequestId}/discussions{/discussionId}/resolve');
- }
+const Vue = window.Vue = require('vue');
+window.Vue.use(require('vue-resource'));
+require('../../vue_shared/vue_resource_interceptor');
- setCSRF() {
- Vue.http.headers.common['X-CSRF-Token'] = $.rails.csrfToken();
- }
+(() => {
+ window.gl = window.gl || {};
- prepareRequest(root) {
- this.setCSRF();
- Vue.http.options.root = root;
+ class ResolveServiceClass {
+ constructor(root) {
+ this.noteResource = Vue.resource(`${root}/notes{/noteId}/resolve`);
+ this.discussionResource = Vue.resource(`${root}/merge_requests{/mergeRequestId}/discussions{/discussionId}/resolve`);
}
- resolve(projectPath, noteId) {
- this.prepareRequest(projectPath);
-
+ resolve(noteId) {
return this.noteResource.save({ noteId }, {});
}
- unresolve(projectPath, noteId) {
- this.prepareRequest(projectPath);
-
+ unresolve(noteId) {
return this.noteResource.delete({ noteId }, {});
}
- toggleResolveForDiscussion(projectPath, mergeRequestId, discussionId) {
+ toggleResolveForDiscussion(mergeRequestId, discussionId) {
const discussion = CommentsStore.state[discussionId];
const isResolved = discussion.isResolved();
let promise;
if (isResolved) {
- promise = this.unResolveAll(projectPath, mergeRequestId, discussionId);
+ promise = this.unResolveAll(mergeRequestId, discussionId);
} else {
- promise = this.resolveAll(projectPath, mergeRequestId, discussionId);
+ promise = this.resolveAll(mergeRequestId, discussionId);
}
promise.then((response) => {
@@ -62,11 +54,9 @@
});
}
- resolveAll(projectPath, mergeRequestId, discussionId) {
+ resolveAll(mergeRequestId, discussionId) {
const discussion = CommentsStore.state[discussionId];
- this.prepareRequest(projectPath);
-
discussion.loading = true;
return this.discussionResource.save({
@@ -75,11 +65,9 @@
}, {});
}
- unResolveAll(projectPath, mergeRequestId, discussionId) {
+ unResolveAll(mergeRequestId, discussionId) {
const discussion = CommentsStore.state[discussionId];
- this.prepareRequest(projectPath);
-
discussion.loading = true;
return this.discussionResource.delete({
@@ -89,5 +77,5 @@
}
}
- w.ResolveService = new ResolveServiceClass();
-})(window);
+ gl.DiffNotesResolveServiceClass = ResolveServiceClass;
+})();
diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6
index f8efca76b13..089ecedeb78 100644
--- a/app/assets/javascripts/dispatcher.js.es6
+++ b/app/assets/javascripts/dispatcher.js.es6
@@ -74,7 +74,7 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'projects:merge_requests:index':
case 'projects:issues:index':
if (gl.FilteredSearchManager) {
- new gl.FilteredSearchManager();
+ new gl.FilteredSearchManager(page === 'projects:issues:index' ? 'issues' : 'merge_requests');
}
Issuable.init();
new gl.IssuableBulkActions({
@@ -97,6 +97,7 @@ const ShortcutsBlob = require('./shortcuts_blob');
break;
case 'projects:milestones:new':
case 'projects:milestones:edit':
+ case 'projects:milestones:update':
new ZenMode();
new gl.DueDateSelectors();
new gl.GLForm($('.milestone-form'));
@@ -107,6 +108,9 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'projects:compare:show':
new gl.Diff();
break;
+ case 'projects:branches:index':
+ gl.AjaxLoadingSpinner.init();
+ break;
case 'projects:issues:new':
case 'projects:issues:edit':
shortcut_handler = new ShortcutsNavigation();
@@ -117,6 +121,7 @@ const ShortcutsBlob = require('./shortcuts_blob');
new gl.IssuableTemplateSelectors();
break;
case 'projects:merge_requests:new':
+ case 'projects:merge_requests:new_diffs':
case 'projects:merge_requests:edit':
new gl.Diff();
shortcut_handler = new ShortcutsNavigation();
@@ -381,4 +386,4 @@ const ShortcutsBlob = require('./shortcuts_blob');
return Dispatcher;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js
index a510eebae1a..646f836aff0 100644
--- a/app/assets/javascripts/dropzone_input.js
+++ b/app/assets/javascripts/dropzone_input.js
@@ -126,13 +126,14 @@ require('./preview_markdown');
};
pasteText = function(text) {
var afterSelection, beforeSelection, caretEnd, caretStart, textEnd;
+ var formattedText = text + "\n\n";
caretStart = $(child)[0].selectionStart;
caretEnd = $(child)[0].selectionEnd;
textEnd = $(child).val().length;
beforeSelection = $(child).val().substring(0, caretStart);
afterSelection = $(child).val().substring(caretEnd, textEnd);
- $(child).val(beforeSelection + text + afterSelection);
- child.get(0).setSelectionRange(caretStart + text.length, caretEnd + text.length);
+ $(child).val(beforeSelection + formattedText + afterSelection);
+ child.get(0).setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length);
return form_textarea.trigger("input");
};
getFilename = function(e) {
@@ -216,4 +217,4 @@ require('./preview_markdown');
return DropzoneInput;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/due_date_select.js.es6 b/app/assets/javascripts/due_date_select.js.es6
index d81d4cf8425..9169fcd7328 100644
--- a/app/assets/javascripts/due_date_select.js.es6
+++ b/app/assets/javascripts/due_date_select.js.es6
@@ -1,4 +1,6 @@
/* eslint-disable wrap-iife, func-names, space-before-function-paren, comma-dangle, prefer-template, consistent-return, class-methods-use-this, arrow-body-style, no-unused-vars, no-underscore-dangle, no-new, max-len, no-sequences, no-unused-expressions, no-param-reassign */
+/* global dateFormat */
+/* global Pikaday */
(function(global) {
class DueDateSelect {
@@ -25,11 +27,14 @@
this.initGlDropdown();
this.initRemoveDueDate();
this.initDatePicker();
- this.initStopPropagation();
}
initGlDropdown() {
this.$dropdown.glDropdown({
+ opened: () => {
+ const calendar = this.$datePicker.data('pikaday');
+ calendar.show();
+ },
hidden: () => {
this.$selectbox.hide();
this.$value.css('display', '');
@@ -38,25 +43,38 @@
}
initDatePicker() {
- this.$datePicker.datepicker({
- dateFormat: 'yy-mm-dd',
- defaultDate: $("input[name='" + this.fieldName + "']").val(),
- altField: "input[name='" + this.fieldName + "']",
- onSelect: () => {
+ const $dueDateInput = $(`input[name='${this.fieldName}']`);
+
+ const calendar = new Pikaday({
+ field: $dueDateInput.get(0),
+ theme: 'gitlab-theme',
+ format: 'yyyy-mm-dd',
+ onSelect: (dateText) => {
+ const formattedDate = dateFormat(new Date(dateText), 'yyyy-mm-dd');
+
+ $dueDateInput.val(formattedDate);
+
if (this.$dropdown.hasClass('js-issue-boards-due-date')) {
- gl.issueBoards.BoardsStore.detail.issue.dueDate = $(`input[name='${this.fieldName}']`).val();
+ gl.issueBoards.BoardsStore.detail.issue.dueDate = $dueDateInput.val();
this.updateIssueBoardIssue();
} else {
- return this.saveDueDate(true);
+ this.saveDueDate(true);
}
}
});
+
+ calendar.setDate(new Date($dueDateInput.val()));
+ this.$datePicker.append(calendar.el);
+ this.$datePicker.data('pikaday', calendar);
}
initRemoveDueDate() {
this.$block.on('click', '.js-remove-due-date', (e) => {
+ const calendar = this.$datePicker.data('pikaday');
e.preventDefault();
+ calendar.setDate(null);
+
if (this.$dropdown.hasClass('js-issue-boards-due-date')) {
gl.issueBoards.BoardsStore.detail.issue.dueDate = '';
this.updateIssueBoardIssue();
@@ -67,12 +85,6 @@
});
}
- initStopPropagation() {
- $(document).off('click', '.ui-datepicker-header a').on('click', '.ui-datepicker-header a', (e) => {
- return e.stopImmediatePropagation();
- });
- }
-
saveDueDate(isDropdown) {
this.parseSelectedDate();
this.prepSelectedDate();
@@ -86,7 +98,7 @@
// Construct Date object manually to avoid buggy dateString support within Date constructor
const dateArray = this.rawSelectedDate.split('-').map(v => parseInt(v, 10));
const dateObj = new Date(dateArray[0], dateArray[1] - 1, dateArray[2]);
- this.displayedDate = $.datepicker.formatDate('M d, yy', dateObj);
+ this.displayedDate = dateFormat(dateObj, 'mmm d, yyyy');
} else {
this.displayedDate = 'No due date';
}
@@ -153,14 +165,25 @@
}
initMilestoneDatePicker() {
- $('.datepicker').datepicker({
- dateFormat: 'yy-mm-dd'
+ $('.datepicker').each(function() {
+ const $datePicker = $(this);
+ const calendar = new Pikaday({
+ field: $datePicker.get(0),
+ theme: 'gitlab-theme',
+ format: 'yyyy-mm-dd',
+ onSelect(dateText) {
+ $datePicker.val(dateFormat(new Date(dateText), 'yyyy-mm-dd'));
+ }
+ });
+ calendar.setDate(new Date($datePicker.val()));
+
+ $datePicker.data('pikaday', calendar);
});
$('.js-clear-due-date,.js-clear-start-date').on('click', (e) => {
e.preventDefault();
- const datepicker = $(e.target).siblings('.datepicker');
- $.datepicker._clearDate(datepicker);
+ const calendar = $(e.target).siblings('.datepicker').data('pikaday');
+ calendar.setDate(null);
});
}
diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6
index 91553bda4dc..4b700a39d44 100644
--- a/app/assets/javascripts/environments/components/environment.js.es6
+++ b/app/assets/javascripts/environments/components/environment.js.es6
@@ -1,223 +1,193 @@
/* eslint-disable no-param-reassign, no-new */
-/* global Vue */
-/* global EnvironmentsService */
/* global Flash */
-window.Vue = require('vue');
+const Vue = window.Vue = require('vue');
window.Vue.use(require('vue-resource'));
-require('../services/environments_service');
-require('./environment_item');
-
-(() => {
- window.gl = window.gl || {};
-
- gl.environmentsList.EnvironmentsComponent = Vue.component('environment-component', {
- props: {
- store: {
- type: Object,
- required: true,
- default: () => ({}),
- },
+const EnvironmentsService = require('../services/environments_service');
+const EnvironmentTable = require('./environments_table');
+const EnvironmentsStore = require('../stores/environments_store');
+require('../../vue_shared/components/table_pagination');
+require('../../lib/utils/common_utils');
+require('../../vue_shared/vue_resource_interceptor');
+
+module.exports = Vue.component('environment-component', {
+
+ components: {
+ 'environment-table': EnvironmentTable,
+ 'table-pagination': gl.VueGlPagination,
+ },
+
+ data() {
+ const environmentsData = document.querySelector('#environments-list-view').dataset;
+ const store = new EnvironmentsStore();
+
+ return {
+ store,
+ state: store.state,
+ visibility: 'available',
+ isLoading: false,
+ cssContainerClass: environmentsData.cssClass,
+ endpoint: environmentsData.environmentsDataEndpoint,
+ canCreateDeployment: environmentsData.canCreateDeployment,
+ canReadEnvironment: environmentsData.canReadEnvironment,
+ canCreateEnvironment: environmentsData.canCreateEnvironment,
+ projectEnvironmentsPath: environmentsData.projectEnvironmentsPath,
+ projectStoppedEnvironmentsPath: environmentsData.projectStoppedEnvironmentsPath,
+ newEnvironmentPath: environmentsData.newEnvironmentPath,
+ helpPagePath: environmentsData.helpPagePath,
+ commitIconSvg: environmentsData.commitIconSvg,
+ playIconSvg: environmentsData.playIconSvg,
+ terminalIconSvg: environmentsData.terminalIconSvg,
+
+ // Pagination Properties,
+ paginationInformation: {},
+ pageNumber: 1,
+ };
+ },
+
+ computed: {
+ scope() {
+ return gl.utils.getParameterByName('scope');
},
- components: {
- 'environment-item': gl.environmentsList.EnvironmentItem,
+ canReadEnvironmentParsed() {
+ return gl.utils.convertPermissionToBoolean(this.canReadEnvironment);
},
- data() {
- const environmentsData = document.querySelector('#environments-list-view').dataset;
-
- return {
- state: this.store.state,
- visibility: 'available',
- isLoading: false,
- cssContainerClass: environmentsData.cssClass,
- endpoint: environmentsData.environmentsDataEndpoint,
- canCreateDeployment: environmentsData.canCreateDeployment,
- canReadEnvironment: environmentsData.canReadEnvironment,
- canCreateEnvironment: environmentsData.canCreateEnvironment,
- projectEnvironmentsPath: environmentsData.projectEnvironmentsPath,
- projectStoppedEnvironmentsPath: environmentsData.projectStoppedEnvironmentsPath,
- newEnvironmentPath: environmentsData.newEnvironmentPath,
- helpPagePath: environmentsData.helpPagePath,
- commitIconSvg: environmentsData.commitIconSvg,
- playIconSvg: environmentsData.playIconSvg,
- terminalIconSvg: environmentsData.terminalIconSvg,
- };
+ canCreateDeploymentParsed() {
+ return gl.utils.convertPermissionToBoolean(this.canCreateDeployment);
},
- computed: {
- scope() {
- return this.$options.getQueryParameter('scope');
- },
-
- canReadEnvironmentParsed() {
- return this.$options.convertPermissionToBoolean(this.canReadEnvironment);
- },
-
- canCreateDeploymentParsed() {
- return this.$options.convertPermissionToBoolean(this.canCreateDeployment);
- },
-
- canCreateEnvironmentParsed() {
- return this.$options.convertPermissionToBoolean(this.canCreateEnvironment);
- },
+ canCreateEnvironmentParsed() {
+ return gl.utils.convertPermissionToBoolean(this.canCreateEnvironment);
},
- /**
- * Fetches all the environments and stores them.
- * Toggles loading property.
- */
- created() {
- gl.environmentsService = new EnvironmentsService(this.endpoint);
-
- const scope = this.$options.getQueryParameter('scope');
- if (scope) {
- this.store.storeVisibility(scope);
- }
-
- this.isLoading = true;
-
- return gl.environmentsService.all()
- .then(resp => resp.json())
- .then((json) => {
- this.store.storeEnvironments(json);
- this.isLoading = false;
- })
- .catch(() => {
- this.isLoading = false;
- new Flash('An error occurred while fetching the environments.', 'alert');
- });
+ },
+
+ /**
+ * Fetches all the environments and stores them.
+ * Toggles loading property.
+ */
+ created() {
+ const scope = gl.utils.getParameterByName('scope') || this.visibility;
+ const pageNumber = gl.utils.getParameterByName('page') || this.pageNumber;
+
+ const endpoint = `${this.endpoint}?scope=${scope}&page=${pageNumber}`;
+
+ const service = new EnvironmentsService(endpoint);
+
+ this.isLoading = true;
+
+ return service.all()
+ .then(resp => ({
+ headers: resp.headers,
+ body: resp.json(),
+ }))
+ .then((response) => {
+ this.store.storeAvailableCount(response.body.available_count);
+ this.store.storeStoppedCount(response.body.stopped_count);
+ this.store.storeEnvironments(response.body.environments);
+ this.store.setPagination(response.headers);
+ })
+ .then(() => {
+ this.isLoading = false;
+ })
+ .catch(() => {
+ this.isLoading = false;
+ new Flash('An error occurred while fetching the environments.', 'alert');
+ });
+ },
+
+ methods: {
+ toggleRow(model) {
+ return this.store.toggleFolder(model.name);
},
/**
- * Transforms the url parameter into an object and
- * returns the one requested.
+ * Will change the page number and update the URL.
*
- * @param {String} param
- * @returns {String} The value of the requested parameter.
+ * @param {Number} pageNumber desired page to go to.
+ * @return {String}
*/
- getQueryParameter(parameter) {
- return window.location.search.substring(1).split('&').reduce((acc, param) => {
- const paramSplited = param.split('=');
- acc[paramSplited[0]] = paramSplited[1];
- return acc;
- }, {})[parameter];
- },
+ changePage(pageNumber) {
+ const param = gl.utils.setParamInURL('page', pageNumber);
- /**
- * Converts permission provided as strings to booleans.
- * @param {String} string
- * @returns {Boolean}
- */
- convertPermissionToBoolean(string) {
- return string === 'true';
+ gl.utils.visitUrl(param);
+ return param;
},
+ },
+
+ template: `
+ <div :class="cssContainerClass">
+ <div class="top-area">
+ <ul v-if="!isLoading" class="nav-links">
+ <li v-bind:class="{ 'active': scope === null || scope === 'available' }">
+ <a :href="projectEnvironmentsPath">
+ Available
+ <span class="badge js-available-environments-count">
+ {{state.availableCounter}}
+ </span>
+ </a>
+ </li>
+ <li v-bind:class="{ 'active' : scope === 'stopped' }">
+ <a :href="projectStoppedEnvironmentsPath">
+ Stopped
+ <span class="badge js-stopped-environments-count">
+ {{state.stoppedCounter}}
+ </span>
+ </a>
+ </li>
+ </ul>
+ <div v-if="canCreateEnvironmentParsed && !isLoading" class="nav-controls">
+ <a :href="newEnvironmentPath" class="btn btn-create">
+ New environment
+ </a>
+ </div>
+ </div>
- methods: {
- toggleRow(model) {
- return this.store.toggleFolder(model.name);
- },
- },
+ <div class="environments-container">
+ <div class="environments-list-loading text-center" v-if="isLoading">
+ <i class="fa fa-spinner fa-spin"></i>
+ </div>
- template: `
- <div :class="cssContainerClass">
- <div class="top-area">
- <ul v-if="!isLoading" class="nav-links">
- <li v-bind:class="{ 'active': scope === undefined }">
- <a :href="projectEnvironmentsPath">
- Available
- <span class="badge js-available-environments-count">
- {{state.availableCounter}}
- </span>
- </a>
- </li><li v-bind:class="{ 'active' : scope === 'stopped' }">
- <a :href="projectStoppedEnvironmentsPath">
- Stopped
- <span class="badge js-stopped-environments-count">
- {{state.stoppedCounter}}
- </span>
- </a>
- </li>
- </ul>
- <div v-if="canCreateEnvironmentParsed && !isLoading" class="nav-controls">
- <a :href="newEnvironmentPath" class="btn btn-create">
- New environment
+ <div class="blank-state blank-state-no-icon"
+ v-if="!isLoading && state.environments.length === 0">
+ <h2 class="blank-state-title js-blank-state-title">
+ You don't have any environments right now.
+ </h2>
+ <p class="blank-state-text">
+ Environments are places where code gets deployed, such as staging or production.
+ <br />
+ <a :href="helpPagePath">
+ Read more about environments
</a>
- </div>
+ </p>
+
+ <a v-if="canCreateEnvironmentParsed"
+ :href="newEnvironmentPath"
+ class="btn btn-create js-new-environment-button">
+ New Environment
+ </a>
</div>
- <div class="environments-container">
- <div class="environments-list-loading text-center" v-if="isLoading">
- <i class="fa fa-spinner fa-spin"></i>
- </div>
-
- <div class="blank-state blank-state-no-icon"
- v-if="!isLoading && state.environments.length === 0">
- <h2 class="blank-state-title js-blank-state-title">
- You don't have any environments right now.
- </h2>
- <p class="blank-state-text">
- Environments are places where code gets deployed, such as staging or production.
- <br />
- <a :href="helpPagePath">
- Read more about environments
- </a>
- </p>
-
- <a
- v-if="canCreateEnvironmentParsed"
- :href="newEnvironmentPath"
- class="btn btn-create js-new-environment-button">
- New Environment
- </a>
- </div>
-
- <div class="table-holder"
- v-if="!isLoading && state.filteredEnvironments.length > 0">
- <table class="table ci-table environments">
- <thead>
- <tr>
- <th class="environments-name">Environment</th>
- <th class="environments-deploy">Last deployment</th>
- <th class="environments-build">Job</th>
- <th class="environments-commit">Commit</th>
- <th class="environments-date">Updated</th>
- <th class="hidden-xs environments-actions"></th>
- </tr>
- </thead>
- <tbody>
- <template v-for="model in state.filteredEnvironments"
- v-bind:model="model">
-
- <tr
- is="environment-item"
- :model="model"
- :toggleRow="toggleRow.bind(model)"
- :can-create-deployment="canCreateDeploymentParsed"
- :can-read-environment="canReadEnvironmentParsed"
- :play-icon-svg="playIconSvg"
- :terminal-icon-svg="terminalIconSvg"
- :commit-icon-svg="commitIconSvg"></tr>
-
- <tr v-if="model.isOpen && model.children && model.children.length > 0"
- is="environment-item"
- v-for="children in model.children"
- :model="children"
- :toggleRow="toggleRow.bind(children)"
- :can-create-deployment="canCreateDeploymentParsed"
- :can-read-environment="canReadEnvironmentParsed"
- :play-icon-svg="playIconSvg"
- :terminal-icon-svg="terminalIconSvg"
- :commit-icon-svg="commitIconSvg">
- </tr>
-
- </template>
- </tbody>
- </table>
- </div>
+ <div class="table-holder"
+ v-if="!isLoading && state.environments.length > 0">
+
+ <environment-table
+ :environments="state.environments"
+ :can-create-deployment="canCreateDeploymentParsed"
+ :can-read-environment="canReadEnvironmentParsed"
+ :play-icon-svg="playIconSvg"
+ :terminal-icon-svg="terminalIconSvg"
+ :commit-icon-svg="commitIconSvg">
+ </environment-table>
+
+ <table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
+ :change="changePage"
+ :pageInfo="state.paginationInformation">
+ </table-pagination>
</div>
</div>
- `,
- });
-})();
+ </div>
+ `,
+});
diff --git a/app/assets/javascripts/environments/components/environment_actions.js.es6 b/app/assets/javascripts/environments/components/environment_actions.js.es6
index ed1c78945db..978d4dd8b6b 100644
--- a/app/assets/javascripts/environments/components/environment_actions.js.es6
+++ b/app/assets/javascripts/environments/components/environment_actions.js.es6
@@ -1,50 +1,43 @@
-/* global Vue */
-
-window.Vue = require('vue');
-
-(() => {
- window.gl = window.gl || {};
- window.gl.environmentsList = window.gl.environmentsList || {};
-
- gl.environmentsList.ActionsComponent = Vue.component('actions-component', {
- props: {
- actions: {
- type: Array,
- required: false,
- default: () => [],
- },
-
- playIconSvg: {
- type: String,
- required: false,
- },
+const Vue = require('vue');
+
+module.exports = Vue.component('actions-component', {
+ props: {
+ actions: {
+ type: Array,
+ required: false,
+ default: () => [],
},
- template: `
- <div class="inline">
- <div class="dropdown">
- <a class="dropdown-new btn btn-default" data-toggle="dropdown">
- <span class="js-dropdown-play-icon-container" v-html="playIconSvg"></span>
- <i class="fa fa-caret-down"></i>
+ playIconSvg: {
+ type: String,
+ required: false,
+ },
+ },
+
+ template: `
+ <div class="btn-group" role="group">
+ <button class="dropdown btn btn-default dropdown-new" data-toggle="dropdown">
+ <span>
+ <span class="js-dropdown-play-icon-container" v-html="playIconSvg"></span>
+ <i class="fa fa-caret-down"></i>
+ </span>
+
+ <ul class="dropdown-menu dropdown-menu-align-right">
+ <li v-for="action in actions">
+ <a :href="action.play_path"
+ data-method="post"
+ rel="nofollow"
+ class="js-manual-action-link">
+
+ <span class="js-action-play-icon-container" v-html="playIconSvg"></span>
+
+ <span>
+ {{action.name}}
+ </span>
</a>
-
- <ul class="dropdown-menu dropdown-menu-align-right">
- <li v-for="action in actions">
- <a :href="action.play_path"
- data-method="post"
- rel="nofollow"
- class="js-manual-action-link">
-
- <span class="js-action-play-icon-container" v-html="playIconSvg"></span>
-
- <span>
- {{action.name}}
- </span>
- </a>
- </li>
- </ul>
- </div>
- </div>
- `,
- });
-})();
+ </li>
+ </ul>
+ </button>
+ </div>
+ `,
+});
diff --git a/app/assets/javascripts/environments/components/environment_external_url.js.es6 b/app/assets/javascripts/environments/components/environment_external_url.js.es6
index 28cc0022d17..2599bba3c59 100644
--- a/app/assets/javascripts/environments/components/environment_external_url.js.es6
+++ b/app/assets/javascripts/environments/components/environment_external_url.js.es6
@@ -1,23 +1,19 @@
-/* global Vue */
+/**
+ * Renders the external url link in environments table.
+ */
+const Vue = require('vue');
-window.Vue = require('vue');
-
-(() => {
- window.gl = window.gl || {};
- window.gl.environmentsList = window.gl.environmentsList || {};
-
- gl.environmentsList.ExternalUrlComponent = Vue.component('external-url-component', {
- props: {
- externalUrl: {
- type: String,
- default: '',
- },
+module.exports = Vue.component('external-url-component', {
+ props: {
+ externalUrl: {
+ type: String,
+ default: '',
},
+ },
- template: `
- <a class="btn external_url" :href="externalUrl" target="_blank">
- <i class="fa fa-external-link"></i>
- </a>
- `,
- });
-})();
+ template: `
+ <a class="btn external_url" :href="externalUrl" target="_blank">
+ <i class="fa fa-external-link"></i>
+ </a>
+ `,
+});
diff --git a/app/assets/javascripts/environments/components/environment_item.js.es6 b/app/assets/javascripts/environments/components/environment_item.js.es6
index 33a99231315..ad9d1d21a79 100644
--- a/app/assets/javascripts/environments/components/environment_item.js.es6
+++ b/app/assets/javascripts/environments/components/environment_item.js.es6
@@ -1,538 +1,536 @@
-/* global Vue */
-/* global timeago */
+const Vue = require('vue');
+const Timeago = require('timeago.js');
-window.Vue = require('vue');
-window.timeago = require('vendor/timeago');
require('../../lib/utils/text_utility');
require('../../vue_shared/components/commit');
-require('./environment_actions');
-require('./environment_external_url');
-require('./environment_stop');
-require('./environment_rollback');
-require('./environment_terminal_button');
+const ActionsComponent = require('./environment_actions');
+const ExternalUrlComponent = require('./environment_external_url');
+const StopComponent = require('./environment_stop');
+const RollbackComponent = require('./environment_rollback');
+const TerminalButtonComponent = require('./environment_terminal_button');
+
+/**
+ * Envrionment Item Component
+ *
+ * Renders a table row for each environment.
+ */
+
+const timeagoInstance = new Timeago();
+
+module.exports = Vue.component('environment-item', {
+
+ components: {
+ 'commit-component': gl.CommitComponent,
+ 'actions-component': ActionsComponent,
+ 'external-url-component': ExternalUrlComponent,
+ 'stop-component': StopComponent,
+ 'rollback-component': RollbackComponent,
+ 'terminal-button-component': TerminalButtonComponent,
+ },
+
+ props: {
+ model: {
+ type: Object,
+ required: true,
+ default: () => ({}),
+ },
-(() => {
- /**
- * Envrionment Item Component
- *
- * Used in a hierarchical structure to show folders with children
- * in a table.
- * Recursive component based on [Tree View](https://vuejs.org/examples/tree-view.html)
- *
- * See this [issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/22539)
- * for more information.15
- */
+ canCreateDeployment: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
- window.gl = window.gl || {};
- window.gl.environmentsList = window.gl.environmentsList || {};
- window.gl.environmentsList.timeagoInstance = new timeago(); // eslint-disable-line
-
- gl.environmentsList.EnvironmentItem = Vue.component('environment-item', {
-
- components: {
- 'commit-component': gl.CommitComponent,
- 'actions-component': gl.environmentsList.ActionsComponent,
- 'external-url-component': gl.environmentsList.ExternalUrlComponent,
- 'stop-component': gl.environmentsList.StopComponent,
- 'rollback-component': gl.environmentsList.RollbackComponent,
- 'terminal-button-component': gl.environmentsList.TerminalButtonComponent,
- },
-
- props: {
- model: {
- type: Object,
- required: true,
- default: () => ({}),
- },
-
- toggleRow: {
- type: Function,
- required: false,
- },
-
- canCreateDeployment: {
- type: Boolean,
- required: false,
- default: false,
- },
-
- canReadEnvironment: {
- type: Boolean,
- required: false,
- default: false,
- },
-
- commitIconSvg: {
- type: String,
- required: false,
- },
-
- playIconSvg: {
- type: String,
- required: false,
- },
-
- terminalIconSvg: {
- type: String,
- required: false,
- },
-
- },
-
- data() {
- return {
- rowClass: {
- 'children-row': this.model['vue-isChildren'],
- },
- };
- },
-
- computed: {
-
- /**
- * If an item has a `children` entry it means it is a folder.
- * Folder items have different behaviours - it is possible to toggle
- * them and show their children.
- *
- * @returns {Boolean|Undefined}
- */
- isFolder() {
- return this.model.children && this.model.children.length > 0;
- },
-
- /**
- * If an item is inside a folder structure will return true.
- * Used for css purposes.
- *
- * @returns {Boolean|undefined}
- */
- isChildren() {
- return this.model['vue-isChildren'];
- },
-
- /**
- * Counts the number of environments in each folder.
- * Used to show a badge with the counter.
- *
- * @returns {Number|Undefined} The number of environments for the current folder.
- */
- childrenCounter() {
- return this.model.children && this.model.children.length;
- },
-
- /**
- * Verifies if `last_deployment` key exists in the current Envrionment.
- * This key is required to render most of the html - this method works has
- * an helper.
- *
- * @returns {Boolean}
- */
- hasLastDeploymentKey() {
- if (this.model.last_deployment &&
- !this.$options.isObjectEmpty(this.model.last_deployment)) {
- return true;
- }
- return false;
- },
-
- /**
- * Verifies is the given environment has manual actions.
- * Used to verify if we should render them or nor.
- *
- * @returns {Boolean|Undefined}
- */
- hasManualActions() {
- return this.model.last_deployment && this.model.last_deployment.manual_actions &&
- this.model.last_deployment.manual_actions.length > 0;
- },
-
- /**
- * Returns the value of the `stop_action?` key provided in the response.
- *
- * @returns {Boolean}
- */
- hasStopAction() {
- return this.model['stop_action?'];
- },
-
- /**
- * Verifies if the `deployable` key is present in `last_deployment` key.
- * Used to verify whether we should or not render the rollback partial.
- *
- * @returns {Boolean|Undefined}
- */
- canRetry() {
- return this.hasLastDeploymentKey &&
- this.model.last_deployment &&
- this.model.last_deployment.deployable;
- },
-
- /**
- * Verifies if the date to be shown is present.
- *
- * @returns {Boolean|Undefined}
- */
- canShowDate() {
- return this.model.last_deployment &&
- this.model.last_deployment.deployable &&
- this.model.last_deployment.deployable !== undefined;
- },
-
- /**
- * Human readable date.
- *
- * @returns {String}
- */
- createdDate() {
- return gl.environmentsList.timeagoInstance.format(
- this.model.last_deployment.deployable.created_at,
- );
- },
-
- /**
- * Returns the manual actions with the name parsed.
- *
- * @returns {Array.<Object>|Undefined}
- */
- manualActions() {
- if (this.hasManualActions) {
- return this.model.last_deployment.manual_actions.map((action) => {
- const parsedAction = {
- name: gl.text.humanize(action.name),
- play_path: action.play_path,
- };
- return parsedAction;
- });
- }
- return [];
- },
-
- /**
- * Builds the string used in the user image alt attribute.
- *
- * @returns {String}
- */
- userImageAltDescription() {
- if (this.model.last_deployment &&
- this.model.last_deployment.user &&
- this.model.last_deployment.user.username) {
- return `${this.model.last_deployment.user.username}'s avatar'`;
- }
- return '';
- },
-
- /**
- * If provided, returns the commit tag.
- *
- * @returns {String|Undefined}
- */
- commitTag() {
- if (this.model.last_deployment &&
- this.model.last_deployment.tag) {
- return this.model.last_deployment.tag;
- }
- return undefined;
- },
-
- /**
- * If provided, returns the commit ref.
- *
- * @returns {Object|Undefined}
- */
- commitRef() {
- if (this.model.last_deployment && this.model.last_deployment.ref) {
- return this.model.last_deployment.ref;
- }
- return undefined;
- },
-
- /**
- * If provided, returns the commit url.
- *
- * @returns {String|Undefined}
- */
- commitUrl() {
- if (this.model.last_deployment &&
- this.model.last_deployment.commit &&
- this.model.last_deployment.commit.commit_path) {
- return this.model.last_deployment.commit.commit_path;
- }
- return undefined;
- },
-
- /**
- * If provided, returns the commit short sha.
- *
- * @returns {String|Undefined}
- */
- commitShortSha() {
- if (this.model.last_deployment &&
- this.model.last_deployment.commit &&
- this.model.last_deployment.commit.short_id) {
- return this.model.last_deployment.commit.short_id;
- }
- return undefined;
- },
-
- /**
- * If provided, returns the commit title.
- *
- * @returns {String|Undefined}
- */
- commitTitle() {
- if (this.model.last_deployment &&
- this.model.last_deployment.commit &&
- this.model.last_deployment.commit.title) {
- return this.model.last_deployment.commit.title;
- }
- return undefined;
- },
-
- /**
- * If provided, returns the commit tag.
- *
- * @returns {Object|Undefined}
- */
- commitAuthor() {
- if (this.model.last_deployment &&
- this.model.last_deployment.commit &&
- this.model.last_deployment.commit.author) {
- return this.model.last_deployment.commit.author;
- }
-
- return undefined;
- },
-
- /**
- * Verifies if the `retry_path` key is present and returns its value.
- *
- * @returns {String|Undefined}
- */
- retryUrl() {
- if (this.model.last_deployment &&
- this.model.last_deployment.deployable &&
- this.model.last_deployment.deployable.retry_path) {
- return this.model.last_deployment.deployable.retry_path;
- }
- return undefined;
- },
-
- /**
- * Verifies if the `last?` key is present and returns its value.
- *
- * @returns {Boolean|Undefined}
- */
- isLastDeployment() {
- return this.model.last_deployment && this.model.last_deployment['last?'];
- },
-
- /**
- * Builds the name of the builds needed to display both the name and the id.
- *
- * @returns {String}
- */
- buildName() {
- if (this.model.last_deployment &&
- this.model.last_deployment.deployable) {
- return `${this.model.last_deployment.deployable.name} #${this.model.last_deployment.deployable.id}`;
- }
- return '';
- },
-
- /**
- * Builds the needed string to show the internal id.
- *
- * @returns {String}
- */
- deploymentInternalId() {
- if (this.model.last_deployment &&
- this.model.last_deployment.iid) {
- return `#${this.model.last_deployment.iid}`;
- }
- return '';
- },
-
- /**
- * Verifies if the user object is present under last_deployment object.
- *
- * @returns {Boolean}
- */
- deploymentHasUser() {
- return !this.$options.isObjectEmpty(this.model.last_deployment) &&
- !this.$options.isObjectEmpty(this.model.last_deployment.user);
- },
-
- /**
- * Returns the user object nested with the last_deployment object.
- * Used to render the template.
- *
- * @returns {Object}
- */
- deploymentUser() {
- if (!this.$options.isObjectEmpty(this.model.last_deployment) &&
- !this.$options.isObjectEmpty(this.model.last_deployment.user)) {
- return this.model.last_deployment.user;
- }
- return {};
- },
-
- /**
- * Verifies if the build name column should be rendered by verifing
- * if all the information needed is present
- * and if the environment is not a folder.
- *
- * @returns {Boolean}
- */
- shouldRenderBuildName() {
- return !this.isFolder &&
- !this.$options.isObjectEmpty(this.model.last_deployment) &&
- !this.$options.isObjectEmpty(this.model.last_deployment.deployable);
- },
-
- /**
- * Verifies if deplyment internal ID should be rendered by verifing
- * if all the information needed is present
- * and if the environment is not a folder.
- *
- * @returns {Boolean}
- */
- shouldRenderDeploymentID() {
- return !this.isFolder &&
- !this.$options.isObjectEmpty(this.model.last_deployment) &&
- this.model.last_deployment.iid !== undefined;
- },
+ canReadEnvironment: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+
+ commitIconSvg: {
+ type: String,
+ required: false,
+ },
+
+ playIconSvg: {
+ type: String,
+ required: false,
},
+ terminalIconSvg: {
+ type: String,
+ required: false,
+ },
+ },
+
+ computed: {
/**
- * Helper to verify if certain given object are empty.
- * Should be replaced by lodash _.isEmpty - https://lodash.com/docs/4.17.2#isEmpty
- * @param {Object} object
- * @returns {Bollean}
+ * Verifies if `last_deployment` key exists in the current Envrionment.
+ * This key is required to render most of the html - this method works has
+ * an helper.
+ *
+ * @returns {Boolean}
*/
- isObjectEmpty(object) {
- for (const key in object) { // eslint-disable-line
- if (hasOwnProperty.call(object, key)) {
- return false;
- }
+ hasLastDeploymentKey() {
+ if (this.model &&
+ this.model.last_deployment &&
+ !this.$options.isObjectEmpty(this.model.last_deployment)) {
+ return true;
}
- return true;
+ return false;
},
- template: `
- <tr>
- <td v-bind:class="{ 'children-row': isChildren}">
- <a v-if="!isFolder"
- class="environment-name"
- :href="model.environment_path">
- {{model.name}}
- </a>
- <span v-else v-on:click="toggleRow(model)" class="folder-name">
- <span class="folder-icon">
- <i v-show="model.isOpen" class="fa fa-caret-down"></i>
- <i v-show="!model.isOpen" class="fa fa-caret-right"></i>
- </span>
-
- <span>
- {{model.name}}
- </span>
-
- <span class="badge">
- {{childrenCounter}}
- </span>
- </span>
- </td>
+ /**
+ * Verifies is the given environment has manual actions.
+ * Used to verify if we should render them or nor.
+ *
+ * @returns {Boolean|Undefined}
+ */
+ hasManualActions() {
+ return this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.manual_actions &&
+ this.model.last_deployment.manual_actions.length > 0;
+ },
+
+ /**
+ * Returns the value of the `stop_action?` key provided in the response.
+ *
+ * @returns {Boolean}
+ */
+ hasStopAction() {
+ return this.model && this.model['stop_action?'];
+ },
+
+ /**
+ * Verifies if the `deployable` key is present in `last_deployment` key.
+ * Used to verify whether we should or not render the rollback partial.
+ *
+ * @returns {Boolean|Undefined}
+ */
+ canRetry() {
+ return this.model &&
+ this.hasLastDeploymentKey &&
+ this.model.last_deployment &&
+ this.model.last_deployment.deployable;
+ },
+
+ /**
+ * Verifies if the date to be shown is present.
+ *
+ * @returns {Boolean|Undefined}
+ */
+ canShowDate() {
+ return this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.deployable &&
+ this.model.last_deployment.deployable !== undefined;
+ },
+
+ /**
+ * Human readable date.
+ *
+ * @returns {String}
+ */
+ createdDate() {
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.deployable &&
+ this.model.last_deployment.deployable.created_at) {
+ return timeagoInstance.format(this.model.last_deployment.deployable.created_at);
+ }
+ return '';
+ },
+
+ /**
+ * Returns the manual actions with the name parsed.
+ *
+ * @returns {Array.<Object>|Undefined}
+ */
+ manualActions() {
+ if (this.hasManualActions) {
+ return this.model.last_deployment.manual_actions.map((action) => {
+ const parsedAction = {
+ name: gl.text.humanize(action.name),
+ play_path: action.play_path,
+ };
+ return parsedAction;
+ });
+ }
+ return [];
+ },
+
+ /**
+ * Builds the string used in the user image alt attribute.
+ *
+ * @returns {String}
+ */
+ userImageAltDescription() {
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.user &&
+ this.model.last_deployment.user.username) {
+ return `${this.model.last_deployment.user.username}'s avatar'`;
+ }
+ return '';
+ },
+
+ /**
+ * If provided, returns the commit tag.
+ *
+ * @returns {String|Undefined}
+ */
+ commitTag() {
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.tag) {
+ return this.model.last_deployment.tag;
+ }
+ return undefined;
+ },
+
+ /**
+ * If provided, returns the commit ref.
+ *
+ * @returns {Object|Undefined}
+ */
+ commitRef() {
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.ref) {
+ return this.model.last_deployment.ref;
+ }
+ return undefined;
+ },
+
+ /**
+ * If provided, returns the commit url.
+ *
+ * @returns {String|Undefined}
+ */
+ commitUrl() {
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.commit &&
+ this.model.last_deployment.commit.commit_path) {
+ return this.model.last_deployment.commit.commit_path;
+ }
+ return undefined;
+ },
+
+ /**
+ * If provided, returns the commit short sha.
+ *
+ * @returns {String|Undefined}
+ */
+ commitShortSha() {
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.commit &&
+ this.model.last_deployment.commit.short_id) {
+ return this.model.last_deployment.commit.short_id;
+ }
+ return undefined;
+ },
+
+ /**
+ * If provided, returns the commit title.
+ *
+ * @returns {String|Undefined}
+ */
+ commitTitle() {
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.commit &&
+ this.model.last_deployment.commit.title) {
+ return this.model.last_deployment.commit.title;
+ }
+ return undefined;
+ },
+
+ /**
+ * If provided, returns the commit tag.
+ *
+ * @returns {Object|Undefined}
+ */
+ commitAuthor() {
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.commit &&
+ this.model.last_deployment.commit.author) {
+ return this.model.last_deployment.commit.author;
+ }
+
+ return undefined;
+ },
+
+ /**
+ * Verifies if the `retry_path` key is present and returns its value.
+ *
+ * @returns {String|Undefined}
+ */
+ retryUrl() {
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.deployable &&
+ this.model.last_deployment.deployable.retry_path) {
+ return this.model.last_deployment.deployable.retry_path;
+ }
+ return undefined;
+ },
+
+ /**
+ * Verifies if the `last?` key is present and returns its value.
+ *
+ * @returns {Boolean|Undefined}
+ */
+ isLastDeployment() {
+ return this.model && this.model.last_deployment &&
+ this.model.last_deployment['last?'];
+ },
- <td class="deployment-column">
- <span v-if="shouldRenderDeploymentID">
- {{deploymentInternalId}}
+ /**
+ * Builds the name of the builds needed to display both the name and the id.
+ *
+ * @returns {String}
+ */
+ buildName() {
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.deployable) {
+ return `${this.model.last_deployment.deployable.name} #${this.model.last_deployment.deployable.id}`;
+ }
+ return '';
+ },
+
+ /**
+ * Builds the needed string to show the internal id.
+ *
+ * @returns {String}
+ */
+ deploymentInternalId() {
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.iid) {
+ return `#${this.model.last_deployment.iid}`;
+ }
+ return '';
+ },
+
+ /**
+ * Verifies if the user object is present under last_deployment object.
+ *
+ * @returns {Boolean}
+ */
+ deploymentHasUser() {
+ return this.model &&
+ !this.$options.isObjectEmpty(this.model.last_deployment) &&
+ !this.$options.isObjectEmpty(this.model.last_deployment.user);
+ },
+
+ /**
+ * Returns the user object nested with the last_deployment object.
+ * Used to render the template.
+ *
+ * @returns {Object}
+ */
+ deploymentUser() {
+ if (this.model &&
+ !this.$options.isObjectEmpty(this.model.last_deployment) &&
+ !this.$options.isObjectEmpty(this.model.last_deployment.user)) {
+ return this.model.last_deployment.user;
+ }
+ return {};
+ },
+
+ /**
+ * Verifies if the build name column should be rendered by verifing
+ * if all the information needed is present
+ * and if the environment is not a folder.
+ *
+ * @returns {Boolean}
+ */
+ shouldRenderBuildName() {
+ return !this.model.isFolder &&
+ !this.$options.isObjectEmpty(this.model.last_deployment) &&
+ !this.$options.isObjectEmpty(this.model.last_deployment.deployable);
+ },
+
+ /**
+ * Verifies the presence of all the keys needed to render the buil_path.
+ *
+ * @return {String}
+ */
+ buildPath() {
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.deployable &&
+ this.model.last_deployment.deployable.build_path) {
+ return this.model.last_deployment.deployable.build_path;
+ }
+
+ return '';
+ },
+
+ /**
+ * Verifies the presence of all the keys needed to render the external_url.
+ *
+ * @return {String}
+ */
+ externalURL() {
+ if (this.model && this.model.external_url) {
+ return this.model.external_url;
+ }
+
+ return '';
+ },
+
+ /**
+ * Verifies if deplyment internal ID should be rendered by verifing
+ * if all the information needed is present
+ * and if the environment is not a folder.
+ *
+ * @returns {Boolean}
+ */
+ shouldRenderDeploymentID() {
+ return !this.model.isFolder &&
+ !this.$options.isObjectEmpty(this.model.last_deployment) &&
+ this.model.last_deployment.iid !== undefined;
+ },
+
+ environmentPath() {
+ if (this.model && this.model.environment_path) {
+ return this.model.environment_path;
+ }
+
+ return '';
+ },
+
+ /**
+ * Constructs folder URL based on the current location and the folder id.
+ *
+ * @return {String}
+ */
+ folderUrl() {
+ return `${window.location.pathname}/folders/${this.model.folderName}`;
+ },
+
+ },
+
+ /**
+ * Helper to verify if certain given object are empty.
+ * Should be replaced by lodash _.isEmpty - https://lodash.com/docs/4.17.2#isEmpty
+ * @param {Object} object
+ * @returns {Bollean}
+ */
+ isObjectEmpty(object) {
+ for (const key in object) { // eslint-disable-line
+ if (hasOwnProperty.call(object, key)) {
+ return false;
+ }
+ }
+ return true;
+ },
+
+ template: `
+ <tr>
+ <td>
+ <a v-if="!model.isFolder"
+ class="environment-name"
+ :href="environmentPath">
+ {{model.name}}
+ </a>
+ <a v-else class="folder-name" :href="folderUrl">
+ <span class="folder-icon">
+ <i class="fa fa-folder" aria-hidden="true"></i>
</span>
- <span v-if="!isFolder && deploymentHasUser">
- by
- <a :href="deploymentUser.web_url" class="js-deploy-user-container">
- <img class="avatar has-tooltip s20"
- :src="deploymentUser.avatar_url"
- :alt="userImageAltDescription"
- :title="deploymentUser.username" />
- </a>
+ <span>
+ {{model.folderName}}
</span>
- </td>
- <td class="environments-build-cell">
- <a v-if="shouldRenderBuildName"
- class="build-link"
- :href="model.last_deployment.deployable.build_path">
- {{buildName}}
- </a>
- </td>
-
- <td>
- <div v-if="!isFolder && hasLastDeploymentKey" class="js-commit-component">
- <commit-component
- :tag="commitTag"
- :commit-ref="commitRef"
- :commit-url="commitUrl"
- :short-sha="commitShortSha"
- :title="commitTitle"
- :author="commitAuthor"
- :commit-icon-svg="commitIconSvg">
- </commit-component>
- </div>
- <p v-if="!isFolder && !hasLastDeploymentKey" class="commit-title">
- No deployments yet
- </p>
- </td>
-
- <td>
- <span
- v-if="!isFolder && canShowDate"
- class="environment-created-date-timeago">
- {{createdDate}}
+ <span class="badge">
+ {{model.size}}
</span>
- </td>
-
- <td class="hidden-xs">
- <div v-if="!isFolder">
- <div v-if="hasManualActions && canCreateDeployment"
- class="inline js-manual-actions-container">
- <actions-component
- :play-icon-svg="playIconSvg"
- :actions="manualActions">
- </actions-component>
- </div>
-
- <div v-if="model.external_url && canReadEnvironment"
- class="inline js-external-url-container">
- <external-url-component
- :external-url="model.external_url">
- </external-url-component>
- </div>
-
- <div v-if="hasStopAction && canCreateDeployment"
- class="inline js-stop-component-container">
- <stop-component
- :stop-url="model.stop_path">
- </stop-component>
- </div>
-
- <div v-if="model.terminal_path"
- class="inline js-terminal-button-container">
- <terminal-button-component
- :terminal-icon-svg="terminalIconSvg"
- :terminal-path="model.terminal_path">
- </terminal-button-component>
- </div>
-
- <div v-if="canRetry && canCreateDeployment"
- class="inline js-rollback-component-container">
- <rollback-component
- :is-last-deployment="isLastDeployment"
- :retry-url="retryUrl">
- </rollback-component>
- </div>
+ </a>
+ </td>
+
+ <td class="deployment-column">
+ <span v-if="shouldRenderDeploymentID">
+ {{deploymentInternalId}}
+ </span>
+
+ <span v-if="!model.isFolder && deploymentHasUser">
+ by
+ <a :href="deploymentUser.web_url" class="js-deploy-user-container">
+ <img class="avatar has-tooltip s20"
+ :src="deploymentUser.avatar_url"
+ :alt="userImageAltDescription"
+ :title="deploymentUser.username" />
+ </a>
+ </span>
+ </td>
+
+ <td class="environments-build-cell">
+ <a v-if="shouldRenderBuildName"
+ class="build-link"
+ :href="buildPath">
+ {{buildName}}
+ </a>
+ </td>
+
+ <td>
+ <div v-if="!model.isFolder && hasLastDeploymentKey" class="js-commit-component">
+ <commit-component
+ :tag="commitTag"
+ :commit-ref="commitRef"
+ :commit-url="commitUrl"
+ :short-sha="commitShortSha"
+ :title="commitTitle"
+ :author="commitAuthor"
+ :commit-icon-svg="commitIconSvg">
+ </commit-component>
+ </div>
+ <p v-if="!model.isFolder && !hasLastDeploymentKey" class="commit-title">
+ No deployments yet
+ </p>
+ </td>
+
+ <td>
+ <span v-if="!model.isFolder && canShowDate"
+ class="environment-created-date-timeago">
+ {{createdDate}}
+ </span>
+ </td>
+
+ <td class="hidden-xs">
+ <div v-if="!model.isFolder">
+ <div class="btn-group" role="group">
+ <actions-component v-if="hasManualActions && canCreateDeployment"
+ :play-icon-svg="playIconSvg"
+ :actions="manualActions">
+ </actions-component>
+
+ <external-url-component v-if="externalURL && canReadEnvironment"
+ :external-url="externalURL">
+ </external-url-component>
+
+ <stop-component v-if="hasStopAction && canCreateDeployment"
+ :stop-url="model.stop_path">
+ </stop-component>
+
+ <terminal-button-component v-if="model && model.terminal_path"
+ :terminal-icon-svg="terminalIconSvg"
+ :terminal-path="model.terminal_path">
+ </terminal-button-component>
+
+ <rollback-component v-if="canRetry && canCreateDeployment"
+ :is-last-deployment="isLastDeployment"
+ :retry-url="retryUrl">
+ </rollback-component>
</div>
- </td>
- </tr>
- `,
- });
-})();
+ </div>
+ </td>
+ </tr>
+ `,
+});
diff --git a/app/assets/javascripts/environments/components/environment_rollback.js.es6 b/app/assets/javascripts/environments/components/environment_rollback.js.es6
index 5938340a128..daf126eb4e8 100644
--- a/app/assets/javascripts/environments/components/environment_rollback.js.es6
+++ b/app/assets/javascripts/environments/components/environment_rollback.js.es6
@@ -1,33 +1,30 @@
-/* global Vue */
+/**
+ * Renders Rollback or Re deploy button in environments table depending
+ * of the provided property `isLastDeployment`
+ */
+const Vue = require('vue');
-window.Vue = require('vue');
-
-(() => {
- window.gl = window.gl || {};
- window.gl.environmentsList = window.gl.environmentsList || {};
-
- gl.environmentsList.RollbackComponent = Vue.component('rollback-component', {
- props: {
- retryUrl: {
- type: String,
- default: '',
- },
+module.exports = Vue.component('rollback-component', {
+ props: {
+ retryUrl: {
+ type: String,
+ default: '',
+ },
- isLastDeployment: {
- type: Boolean,
- default: true,
- },
+ isLastDeployment: {
+ type: Boolean,
+ default: true,
},
+ },
- template: `
- <a class="btn" :href="retryUrl" data-method="post" rel="nofollow">
- <span v-if="isLastDeployment">
- Re-deploy
- </span>
- <span v-else>
- Rollback
- </span>
- </a>
- `,
- });
-})();
+ template: `
+ <a class="btn" :href="retryUrl" data-method="post" rel="nofollow">
+ <span v-if="isLastDeployment">
+ Re-deploy
+ </span>
+ <span v-else>
+ Rollback
+ </span>
+ </a>
+ `,
+});
diff --git a/app/assets/javascripts/environments/components/environment_stop.js.es6 b/app/assets/javascripts/environments/components/environment_stop.js.es6
index be9526989a0..96983a19568 100644
--- a/app/assets/javascripts/environments/components/environment_stop.js.es6
+++ b/app/assets/javascripts/environments/components/environment_stop.js.es6
@@ -1,27 +1,24 @@
-/* global Vue */
+/**
+ * Renders the stop "button" that allows stop an environment.
+ * Used in environments table.
+ */
+const Vue = require('vue');
-window.Vue = require('vue');
-
-(() => {
- window.gl = window.gl || {};
- window.gl.environmentsList = window.gl.environmentsList || {};
-
- gl.environmentsList.StopComponent = Vue.component('stop-component', {
- props: {
- stopUrl: {
- type: String,
- default: '',
- },
+module.exports = Vue.component('stop-component', {
+ props: {
+ stopUrl: {
+ type: String,
+ default: '',
},
+ },
- template: `
- <a class="btn stop-env-link"
- :href="stopUrl"
- data-confirm="Are you sure you want to stop this environment?"
- data-method="post"
- rel="nofollow">
- <i class="fa fa-stop stop-env-icon"></i>
- </a>
- `,
- });
-})();
+ template: `
+ <a class="btn stop-env-link"
+ :href="stopUrl"
+ data-confirm="Are you sure you want to stop this environment?"
+ data-method="post"
+ rel="nofollow">
+ <i class="fa fa-stop stop-env-icon" aria-hidden="true"></i>
+ </a>
+ `,
+});
diff --git a/app/assets/javascripts/environments/components/environment_terminal_button.js.es6 b/app/assets/javascripts/environments/components/environment_terminal_button.js.es6
index a3ad063f7cb..481e0d15e7a 100644
--- a/app/assets/javascripts/environments/components/environment_terminal_button.js.es6
+++ b/app/assets/javascripts/environments/components/environment_terminal_button.js.es6
@@ -1,28 +1,25 @@
-/* global Vue */
+/**
+ * Renders a terminal button to open a web terminal.
+ * Used in environments table.
+ */
+const Vue = require('vue');
-window.Vue = require('vue');
-
-(() => {
- window.gl = window.gl || {};
- window.gl.environmentsList = window.gl.environmentsList || {};
-
- gl.environmentsList.TerminalButtonComponent = Vue.component('terminal-button-component', {
- props: {
- terminalPath: {
- type: String,
- default: '',
- },
- terminalIconSvg: {
- type: String,
- default: '',
- },
+module.exports = Vue.component('terminal-button-component', {
+ props: {
+ terminalPath: {
+ type: String,
+ default: '',
+ },
+ terminalIconSvg: {
+ type: String,
+ default: '',
},
+ },
- template: `
- <a class="btn terminal-button"
- :href="terminalPath">
- <span class="js-terminal-icon-container" v-html="terminalIconSvg"></span>
- </a>
- `,
- });
-})();
+ template: `
+ <a class="btn terminal-button"
+ :href="terminalPath">
+ <span class="js-terminal-icon-container" v-html="terminalIconSvg"></span>
+ </a>
+ `,
+});
diff --git a/app/assets/javascripts/environments/components/environments_table.js.es6 b/app/assets/javascripts/environments/components/environments_table.js.es6
new file mode 100644
index 00000000000..fd35d77fd3d
--- /dev/null
+++ b/app/assets/javascripts/environments/components/environments_table.js.es6
@@ -0,0 +1,74 @@
+/**
+ * Render environments table.
+ */
+const Vue = require('vue');
+const EnvironmentItem = require('./environment_item');
+
+module.exports = Vue.component('environment-table-component', {
+
+ components: {
+ 'environment-item': EnvironmentItem,
+ },
+
+ props: {
+ environments: {
+ type: Array,
+ required: true,
+ default: () => ([]),
+ },
+
+ canReadEnvironment: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+
+ canCreateDeployment: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+
+ commitIconSvg: {
+ type: String,
+ required: false,
+ },
+
+ playIconSvg: {
+ type: String,
+ required: false,
+ },
+
+ terminalIconSvg: {
+ type: String,
+ required: false,
+ },
+ },
+
+ template: `
+ <table class="table ci-table environments">
+ <thead>
+ <tr>
+ <th class="environments-name">Environment</th>
+ <th class="environments-deploy">Last deployment</th>
+ <th class="environments-build">Job</th>
+ <th class="environments-commit">Commit</th>
+ <th class="environments-date">Updated</th>
+ <th class="hidden-xs environments-actions"></th>
+ </tr>
+ </thead>
+ <tbody>
+ <template v-for="model in environments"
+ v-bind:model="model">
+ <tr is="environment-item"
+ :model="model"
+ :can-create-deployment="canCreateDeployment"
+ :can-read-environment="canReadEnvironment"
+ :play-icon-svg="playIconSvg"
+ :terminal-icon-svg="terminalIconSvg"
+ :commit-icon-svg="commitIconSvg"></tr>
+ </template>
+ </tbody>
+ </table>
+ `,
+});
diff --git a/app/assets/javascripts/environments/environments_bundle.js.es6 b/app/assets/javascripts/environments/environments_bundle.js.es6
index 05c59d92fd4..7bbba91bc10 100644
--- a/app/assets/javascripts/environments/environments_bundle.js.es6
+++ b/app/assets/javascripts/environments/environments_bundle.js.es6
@@ -1,7 +1,4 @@
-window.Vue = require('vue');
-require('./stores/environments_store');
-require('./components/environment');
-require('../vue_shared/vue_resource_interceptor');
+const EnvironmentsComponent = require('./components/environment');
$(() => {
window.gl = window.gl || {};
@@ -9,14 +6,8 @@ $(() => {
if (gl.EnvironmentsListApp) {
gl.EnvironmentsListApp.$destroy(true);
}
- const Store = gl.environmentsList.EnvironmentsStore;
- gl.EnvironmentsListApp = new gl.environmentsList.EnvironmentsComponent({
+ gl.EnvironmentsListApp = new EnvironmentsComponent({
el: document.querySelector('#environments-list-view'),
-
- propsData: {
- store: Store.create(),
- },
-
});
});
diff --git a/app/assets/javascripts/environments/folder/environments_folder_bundle.js.es6 b/app/assets/javascripts/environments/folder/environments_folder_bundle.js.es6
new file mode 100644
index 00000000000..d2ca465351a
--- /dev/null
+++ b/app/assets/javascripts/environments/folder/environments_folder_bundle.js.es6
@@ -0,0 +1,13 @@
+const EnvironmentsFolderComponent = require('./environments_folder_view');
+
+$(() => {
+ window.gl = window.gl || {};
+
+ if (gl.EnvironmentsListFolderApp) {
+ gl.EnvironmentsListFolderApp.$destroy(true);
+ }
+
+ gl.EnvironmentsListFolderApp = new EnvironmentsFolderComponent({
+ el: document.querySelector('#environments-folder-list-view'),
+ });
+});
diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.js.es6 b/app/assets/javascripts/environments/folder/environments_folder_view.js.es6
new file mode 100644
index 00000000000..53d52965758
--- /dev/null
+++ b/app/assets/javascripts/environments/folder/environments_folder_view.js.es6
@@ -0,0 +1,182 @@
+/* eslint-disable no-param-reassign, no-new */
+/* global Flash */
+
+const Vue = window.Vue = require('vue');
+window.Vue.use(require('vue-resource'));
+const EnvironmentsService = require('../services/environments_service');
+const EnvironmentTable = require('../components/environments_table');
+const EnvironmentsStore = require('../stores/environments_store');
+require('../../vue_shared/components/table_pagination');
+require('../../lib/utils/common_utils');
+require('../../vue_shared/vue_resource_interceptor');
+
+module.exports = Vue.component('environment-folder-view', {
+
+ components: {
+ 'environment-table': EnvironmentTable,
+ 'table-pagination': gl.VueGlPagination,
+ },
+
+ data() {
+ const environmentsData = document.querySelector('#environments-folder-list-view').dataset;
+ const store = new EnvironmentsStore();
+ const pathname = window.location.pathname;
+ const endpoint = `${pathname}.json`;
+ const folderName = pathname.substr(pathname.lastIndexOf('/') + 1);
+
+ return {
+ store,
+ folderName,
+ endpoint,
+ state: store.state,
+ visibility: 'available',
+ isLoading: false,
+ cssContainerClass: environmentsData.cssClass,
+ canCreateDeployment: environmentsData.canCreateDeployment,
+ canReadEnvironment: environmentsData.canReadEnvironment,
+
+ // svgs
+ commitIconSvg: environmentsData.commitIconSvg,
+ playIconSvg: environmentsData.playIconSvg,
+ terminalIconSvg: environmentsData.terminalIconSvg,
+
+ // Pagination Properties,
+ paginationInformation: {},
+ pageNumber: 1,
+ };
+ },
+
+ computed: {
+ scope() {
+ return gl.utils.getParameterByName('scope');
+ },
+
+ canReadEnvironmentParsed() {
+ return gl.utils.convertPermissionToBoolean(this.canReadEnvironment);
+ },
+
+ canCreateDeploymentParsed() {
+ return gl.utils.convertPermissionToBoolean(this.canCreateDeployment);
+ },
+
+ /**
+ * URL to link in the stopped tab.
+ *
+ * @return {String}
+ */
+ stoppedPath() {
+ return `${window.location.pathname}?scope=stopped`;
+ },
+
+ /**
+ * URL to link in the available tab.
+ *
+ * @return {String}
+ */
+ availablePath() {
+ return window.location.pathname;
+ },
+ },
+
+ /**
+ * Fetches all the environments and stores them.
+ * Toggles loading property.
+ */
+ created() {
+ const scope = gl.utils.getParameterByName('scope') || this.visibility;
+ const pageNumber = gl.utils.getParameterByName('page') || this.pageNumber;
+
+ const endpoint = `${this.endpoint}?scope=${scope}&page=${pageNumber}`;
+
+ const service = new EnvironmentsService(endpoint);
+
+ this.isLoading = true;
+
+ return service.all()
+ .then(resp => ({
+ headers: resp.headers,
+ body: resp.json(),
+ }))
+ .then((response) => {
+ this.store.storeAvailableCount(response.body.available_count);
+ this.store.storeStoppedCount(response.body.stopped_count);
+ this.store.storeEnvironments(response.body.environments);
+ this.store.setPagination(response.headers);
+ })
+ .then(() => {
+ this.isLoading = false;
+ })
+ .catch(() => {
+ this.isLoading = false;
+ new Flash('An error occurred while fetching the environments.', 'alert');
+ });
+ },
+
+ methods: {
+ /**
+ * Will change the page number and update the URL.
+ *
+ * @param {Number} pageNumber desired page to go to.
+ */
+ changePage(pageNumber) {
+ const param = gl.utils.setParamInURL('page', pageNumber);
+
+ gl.utils.visitUrl(param);
+ return param;
+ },
+ },
+
+ template: `
+ <div :class="cssContainerClass">
+ <div class="top-area" v-if="!isLoading">
+
+ <h4 class="js-folder-name environments-folder-name">
+ Environments / <b>{{folderName}}</b>
+ </h4>
+
+ <ul class="nav-links">
+ <li v-bind:class="{ 'active': scope === null || scope === 'available' }">
+ <a :href="availablePath" class="js-available-environments-folder-tab">
+ Available
+ <span class="badge js-available-environments-count">
+ {{state.availableCounter}}
+ </span>
+ </a>
+ </li>
+ <li v-bind:class="{ 'active' : scope === 'stopped' }">
+ <a :href="stoppedPath" class="js-stopped-environments-folder-tab">
+ Stopped
+ <span class="badge js-stopped-environments-count">
+ {{state.stoppedCounter}}
+ </span>
+ </a>
+ </li>
+ </ul>
+ </div>
+
+ <div class="environments-container">
+ <div class="environments-list-loading text-center" v-if="isLoading">
+ <i class="fa fa-spinner fa-spin"></i>
+ </div>
+
+ <div class="table-holder"
+ v-if="!isLoading && state.environments.length > 0">
+
+ <environment-table
+ :environments="state.environments"
+ :can-create-deployment="canCreateDeploymentParsed"
+ :can-read-environment="canReadEnvironmentParsed"
+ :play-icon-svg="playIconSvg"
+ :terminal-icon-svg="terminalIconSvg"
+ :commit-icon-svg="commitIconSvg">
+ </environment-table>
+
+ <table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
+ :change="changePage"
+ :pageInfo="state.paginationInformation">
+ </table-pagination>
+ </div>
+ </div>
+ </div>
+ `,
+});
diff --git a/app/assets/javascripts/environments/services/environments_service.js.es6 b/app/assets/javascripts/environments/services/environments_service.js.es6
index fab8d977f58..9cef335868e 100644
--- a/app/assets/javascripts/environments/services/environments_service.js.es6
+++ b/app/assets/javascripts/environments/services/environments_service.js.es6
@@ -1,20 +1,8 @@
-/* globals Vue */
-/* eslint-disable no-unused-vars, no-param-reassign */
+const Vue = require('vue');
class EnvironmentsService {
-
- constructor(root) {
- Vue.http.options.root = root;
-
- this.environments = Vue.resource(root);
-
- Vue.http.interceptors.push((request, next) => {
- // needed in order to not break the tests.
- if ($.rails) {
- request.headers['X-CSRF-Token'] = $.rails.csrfToken();
- }
- next();
- });
+ constructor(endpoint) {
+ this.environments = Vue.resource(endpoint);
}
all() {
@@ -22,4 +10,4 @@ class EnvironmentsService {
}
}
-window.EnvironmentsService = EnvironmentsService;
+module.exports = EnvironmentsService;
diff --git a/app/assets/javascripts/environments/stores/environments_store.js.es6 b/app/assets/javascripts/environments/stores/environments_store.js.es6
index 9b4090100da..15cd9bde08e 100644
--- a/app/assets/javascripts/environments/stores/environments_store.js.es6
+++ b/app/assets/javascripts/environments/stores/environments_store.js.es6
@@ -1,190 +1,90 @@
-/* eslint-disable no-param-reassign */
-(() => {
- window.gl = window.gl || {};
- window.gl.environmentsList = window.gl.environmentsList || {};
-
- gl.environmentsList.EnvironmentsStore = {
- state: {},
-
- create() {
- this.state.environments = [];
- this.state.stoppedCounter = 0;
- this.state.availableCounter = 0;
- this.state.visibility = 'available';
- this.state.filteredEnvironments = [];
-
- return this;
- },
-
- /**
- * In order to display a tree view we need to modify the received
- * data in to a tree structure based on `environment_type`
- * sorted alphabetically.
- * In each children a `vue-` property will be added. This property will be
- * used to know if an item is a children mostly for css purposes. This is
- * needed because the children row is a fragment instance and therfore does
- * not accept non-prop attributes.
- *
- *
- * @example
- * it will transform this:
- * [
- * { name: "environment", environment_type: "review" },
- * { name: "environment_1", environment_type: null }
- * { name: "environment_2, environment_type: "review" }
- * ]
- * into this:
- * [
- * { name: "review", children:
- * [
- * { name: "environment", environment_type: "review", vue-isChildren: true},
- * { name: "environment_2", environment_type: "review", vue-isChildren: true}
- * ]
- * },
- * {name: "environment_1", environment_type: null}
- * ]
- *
- *
- * @param {Array} environments List of environments.
- * @returns {Array} Tree structured array with the received environments.
- */
- storeEnvironments(environments = []) {
- this.state.stoppedCounter = this.countByState(environments, 'stopped');
- this.state.availableCounter = this.countByState(environments, 'available');
-
- const environmentsTree = environments.reduce((acc, environment) => {
- if (environment.environment_type !== null) {
- const occurs = acc.filter(element => element.children &&
- element.name === environment.environment_type);
-
- environment['vue-isChildren'] = true;
-
- if (occurs.length) {
- acc[acc.indexOf(occurs[0])].children.push(environment);
- acc[acc.indexOf(occurs[0])].children.slice().sort(this.sortByName);
- } else {
- acc.push({
- name: environment.environment_type,
- children: [environment],
- isOpen: false,
- 'vue-isChildren': environment['vue-isChildren'],
- });
- }
- } else {
- acc.push(environment);
- }
-
- return acc;
- }, []).slice().sort(this.sortByName);
-
- this.state.environments = environmentsTree;
-
- this.filterEnvironmentsByVisibility(this.state.environments);
-
- return environmentsTree;
- },
-
- storeVisibility(visibility) {
- this.state.visibility = visibility;
- },
- /**
- * Given the visibility prop provided by the url query parameter and which
- * changes according to the active tab we need to filter which environments
- * should be visible.
- *
- * The environments array is a recursive tree structure and we need to filter
- * both root level environments and children environments.
- *
- * In order to acomplish that, both `filterState` and `filterEnvironmentsByVisibility`
- * functions work together.
- * The first one works as the filter that verifies if the given environment matches
- * the given state.
- * The second guarantees both root level and children elements are filtered as well.
- *
- * Given array of environments will return only
- * the environments that match the state stored.
- *
- * @param {Array} array
- * @return {Array}
- */
- filterEnvironmentsByVisibility(arr) {
- const filteredEnvironments = arr.map((item) => {
- if (item.children) {
- const filteredChildren = this.filterEnvironmentsByVisibility(
- item.children,
- ).filter(Boolean);
-
- if (filteredChildren.length) {
- item.children = filteredChildren;
- return item;
- }
- }
-
- return this.filterState(this.state.visibility, item);
- }).filter(Boolean);
-
- this.state.filteredEnvironments = filteredEnvironments;
- return filteredEnvironments;
- },
-
- /**
- * Given the state and the environment,
- * returns only if the environment state matches the one provided.
- *
- * @param {String} state
- * @param {Object} environment
- * @return {Object}
- */
- filterState(state, environment) {
- return environment.state === state && environment;
- },
-
- /**
- * Toggles folder open property given the environment type.
- *
- * @param {String} envType
- * @return {Array}
- */
- toggleFolder(envType) {
- const environments = this.state.environments;
-
- const environmentsCopy = environments.map((env) => {
- if (env['vue-isChildren'] && env.name === envType) {
- env.isOpen = !env.isOpen;
- }
-
- return env;
- });
-
- this.state.environments = environmentsCopy;
-
- return environmentsCopy;
- },
-
- /**
- * Given an array of environments, returns the number of environments
- * that have the given state.
- *
- * @param {Array} environments
- * @param {String} state
- * @returns {Number}
- */
- countByState(environments, state) {
- return environments.filter(env => env.state === state).length;
- },
-
- /**
- * Sorts the two objects provided by their name.
- *
- * @param {Object} a
- * @param {Object} b
- * @returns {Number}
- */
- sortByName(a, b) {
- const nameA = a.name.toUpperCase();
- const nameB = b.name.toUpperCase();
-
- return nameA < nameB ? -1 : nameA > nameB ? 1 : 0; // eslint-disable-line
- },
- };
-})();
+require('~/lib/utils/common_utils');
+/**
+ * Environments Store.
+ *
+ * Stores received environments, count of stopped environments and count of
+ * available environments.
+ */
+class EnvironmentsStore {
+ constructor() {
+ this.state = {};
+ this.state.environments = [];
+ this.state.stoppedCounter = 0;
+ this.state.availableCounter = 0;
+ this.state.paginationInformation = {};
+
+ return this;
+ }
+
+ /**
+ *
+ * Stores the received environments.
+ *
+ * In the main environments endpoint, each environment has the following schema
+ * { name: String, size: Number, latest: Object }
+ * In the endpoint to retrieve environments from each folder, the environment does
+ * not have the `latest` key and the data is all in the root level.
+ * To avoid doing this check in the view, we store both cases the same by extracting
+ * what is inside the `latest` key.
+ *
+ * If the `size` is bigger than 1, it means it should be rendered as a folder.
+ * In those cases we add `isFolder` key in order to render it properly.
+ *
+ * @param {Array} environments
+ * @returns {Array}
+ */
+ storeEnvironments(environments = []) {
+ const filteredEnvironments = environments.map((env) => {
+ let filtered = {};
+
+ if (env.size > 1) {
+ filtered = Object.assign({}, env, { isFolder: true, folderName: env.name });
+ }
+
+ if (env.latest) {
+ filtered = Object.assign(filtered, env, env.latest);
+ delete filtered.latest;
+ } else {
+ filtered = Object.assign(filtered, env);
+ }
+
+ return filtered;
+ });
+
+ this.state.environments = filteredEnvironments;
+
+ return filteredEnvironments;
+ }
+
+ setPagination(pagination = {}) {
+ const normalizedHeaders = gl.utils.normalizeHeaders(pagination);
+ const paginationInformation = gl.utils.parseIntPagination(normalizedHeaders);
+
+ this.state.paginationInformation = paginationInformation;
+ return paginationInformation;
+ }
+
+ /**
+ * Stores the number of available environments.
+ *
+ * @param {Number} count = 0
+ * @return {Number}
+ */
+ storeAvailableCount(count = 0) {
+ this.state.availableCounter = count;
+ return count;
+ }
+
+ /**
+ * Stores the number of closed environments.
+ *
+ * @param {Number} count = 0
+ * @return {Number}
+ */
+ storeStoppedCount(count = 0) {
+ this.state.stoppedCounter = count;
+ return count;
+ }
+}
+
+module.exports = EnvironmentsStore;
diff --git a/app/assets/javascripts/extensions/jquery.js b/app/assets/javascripts/extensions/jquery.js
index d3b58b2707a..1a489b859e8 100644
--- a/app/assets/javascripts/extensions/jquery.js
+++ b/app/assets/javascripts/extensions/jquery.js
@@ -13,4 +13,4 @@
return $(this).removeAttr('disabled').removeClass('disabled');
}
});
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/files_comment_button.js b/app/assets/javascripts/files_comment_button.js
index 895a872568d..698870d0ce1 100644
--- a/app/assets/javascripts/files_comment_button.js
+++ b/app/assets/javascripts/files_comment_button.js
@@ -144,4 +144,4 @@
}
});
};
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6
index 572c221929a..9e92d544bef 100644
--- a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6
+++ b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6
@@ -37,23 +37,18 @@ require('./filtered_search_dropdown');
}
renderContent() {
- const dropdownData = [{
- icon: 'fa-pencil',
- hint: 'author:',
- tag: '&lt;@author&gt;',
- }, {
- icon: 'fa-user',
- hint: 'assignee:',
- tag: '&lt;@assignee&gt;',
- }, {
- icon: 'fa-clock-o',
- hint: 'milestone:',
- tag: '&lt;%milestone&gt;',
- }, {
- icon: 'fa-tag',
- hint: 'label:',
- tag: '&lt;~label&gt;',
- }];
+ const dropdownData = [];
+
+ [].forEach.call(this.input.parentElement.querySelectorAll('.dropdown-menu'), (dropdownMenu) => {
+ const { icon, hint, tag } = dropdownMenu.dataset;
+ if (icon && hint && tag) {
+ dropdownData.push({
+ icon: `fa-${icon}`,
+ hint,
+ tag: `&lt;${tag}&gt;`,
+ });
+ }
+ });
this.droplab.changeHookList(this.hookId, this.dropdown, [droplabFilter], this.config);
this.droplab.setData(this.hookId, dropdownData);
diff --git a/app/assets/javascripts/filtered_search/filtered_search_bundle.js b/app/assets/javascripts/filtered_search/filtered_search_bundle.js
index 392f1835966..faaba994f46 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_bundle.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_bundle.js
@@ -1,3 +1,9 @@
-function requireAll(context) { return context.keys().map(context); }
-
-requireAll(require.context('./', true, /^\.\/(?!filtered_search_bundle).*\.(js|es6)$/));
+require('./dropdown_hint');
+require('./dropdown_non_user');
+require('./dropdown_user');
+require('./dropdown_utils');
+require('./filtered_search_dropdown_manager');
+require('./filtered_search_dropdown');
+require('./filtered_search_manager');
+require('./filtered_search_token_keys');
+require('./filtered_search_tokenizer');
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6
index 859d6515531..fbc72a3001a 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6
@@ -4,7 +4,7 @@
class FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) {
this.droplab = droplab;
- this.hookId = input.getAttribute('data-id');
+ this.hookId = input && input.getAttribute('data-id');
this.input = input;
this.filter = filter;
this.dropdown = dropdown;
@@ -52,8 +52,9 @@
}
renderContent(forceShowList = false) {
- if (forceShowList && this.getCurrentHook().list.hidden) {
- this.getCurrentHook().list.show();
+ const currentHook = this.getCurrentHook();
+ if (forceShowList && currentHook && currentHook.list.hidden) {
+ currentHook.list.show();
}
}
@@ -92,18 +93,24 @@
}
hideDropdown() {
- this.getCurrentHook().list.hide();
+ const currentHook = this.getCurrentHook();
+ if (currentHook) {
+ currentHook.list.hide();
+ }
}
resetFilters() {
const hook = this.getCurrentHook();
- const data = hook.list.data;
- const results = data.map((o) => {
- const updated = o;
- updated.droplab_hidden = false;
- return updated;
- });
- hook.list.render(results);
+
+ if (hook) {
+ const data = hook.list.data;
+ const results = data.map((o) => {
+ const updated = o;
+ updated.droplab_hidden = false;
+ return updated;
+ });
+ hook.list.render(results);
+ }
}
}
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6
index 547989a6ff5..cecd3518ce3 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6
@@ -2,9 +2,12 @@
(() => {
class FilteredSearchDropdownManager {
- constructor() {
+ constructor(baseEndpoint = '', page) {
+ this.baseEndpoint = baseEndpoint.replace(/\/$/, '');
this.tokenizer = gl.FilteredSearchTokenizer;
+ this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys;
this.filteredSearchInput = document.querySelector('.filtered-search');
+ this.page = page;
this.setupMapping();
@@ -38,13 +41,13 @@
milestone: {
reference: null,
gl: 'DropdownNonUser',
- extraArguments: ['milestones.json', '%'],
+ extraArguments: [`${this.baseEndpoint}/milestones.json`, '%'],
element: document.querySelector('#js-dropdown-milestone'),
},
label: {
reference: null,
gl: 'DropdownNonUser',
- extraArguments: ['labels.json', '~'],
+ extraArguments: [`${this.baseEndpoint}/labels.json`, '~'],
element: document.querySelector('#js-dropdown-label'),
},
hint: {
@@ -149,7 +152,7 @@
this.droplab = new DropLab();
}
- const match = gl.FilteredSearchTokenKeys.searchByKey(dropdownName.toLowerCase());
+ const match = this.filteredSearchTokenKeys.searchByKey(dropdownName.toLowerCase());
const shouldOpenFilterDropdown = match && this.currentDropdown !== match.key
&& this.mapping[match.key];
const shouldOpenHintDropdown = !match && this.currentDropdown !== 'hint';
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6
index 4e02ab7c8c1..bbafead0305 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6
@@ -1,12 +1,13 @@
(() => {
class FilteredSearchManager {
- constructor() {
+ constructor(page) {
this.filteredSearchInput = document.querySelector('.filtered-search');
this.clearSearchButton = document.querySelector('.clear-search');
+ this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys;
if (this.filteredSearchInput) {
this.tokenizer = gl.FilteredSearchTokenizer;
- this.dropdownManager = new gl.FilteredSearchDropdownManager();
+ this.dropdownManager = new gl.FilteredSearchDropdownManager(this.filteredSearchInput.getAttribute('data-base-endpoint') || '', page);
this.bindEvents();
this.loadSearchParamsFromURL();
@@ -117,8 +118,8 @@
const keyParam = decodeURIComponent(split[0]);
const value = split[1];
- // Check if it matches edge conditions listed in gl.FilteredSearchTokenKeys
- const condition = gl.FilteredSearchTokenKeys.searchByConditionUrl(p);
+ // Check if it matches edge conditions listed in this.filteredSearchTokenKeys
+ const condition = this.filteredSearchTokenKeys.searchByConditionUrl(p);
if (condition) {
inputValues.push(`${condition.tokenKey}:${condition.value}`);
@@ -126,7 +127,7 @@
// Sanitize value since URL converts spaces into +
// Replace before decode so that we know what was originally + versus the encoded +
const sanitizedValue = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : value;
- const match = gl.FilteredSearchTokenKeys.searchByKeyParam(keyParam);
+ const match = this.filteredSearchTokenKeys.searchByKeyParam(keyParam);
if (match) {
const indexOf = keyParam.indexOf('_');
@@ -171,9 +172,9 @@
paths.push(`state=${currentState}`);
tokens.forEach((token) => {
- const condition = gl.FilteredSearchTokenKeys
+ const condition = this.filteredSearchTokenKeys
.searchByConditionKeyValue(token.key, token.value.toLowerCase());
- const { param } = gl.FilteredSearchTokenKeys.searchByKey(token.key);
+ const { param } = this.filteredSearchTokenKeys.searchByKey(token.key) || {};
const keyParam = param ? `${token.key}_${param}` : token.key;
let tokenPath = '';
diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6
index cf53845a48b..9bf1b1ced88 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6
+++ b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6
@@ -1,9 +1,12 @@
+require('./filtered_search_token_keys');
+
(() => {
class FilteredSearchTokenizer {
static processTokens(input) {
+ const allowedKeys = gl.FilteredSearchTokenKeys.get().map(i => i.key);
// Regex extracts `(token):(symbol)(value)`
// Values that start with a double quote must end in a double quote (same for single)
- const tokenRegex = /(\w+):([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\S+))/g;
+ const tokenRegex = new RegExp(`(${allowedKeys.join('|')}):([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\\S+))`, 'g');
const tokens = [];
let lastToken = null;
const searchToken = input.replace(tokenRegex, (match, key, symbol, v1, v2, v3) => {
diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js
index 249fe23d4cb..730104b89f9 100644
--- a/app/assets/javascripts/flash.js
+++ b/app/assets/javascripts/flash.js
@@ -39,4 +39,4 @@
return Flash;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6
index 7f1f2a5d278..60d6658dc16 100644
--- a/app/assets/javascripts/gfm_auto_complete.js.es6
+++ b/app/assets/javascripts/gfm_auto_complete.js.es6
@@ -83,12 +83,12 @@
_a = decodeURI("%C3%80");
_y = decodeURI("%C3%BF");
- regexp = new RegExp("^(?:\\B|[^a-zA-Z0-9_" + atSymbolsWithoutBar + "]|\\s)" + flag + "(?![" + atSymbolsWithBar + "])(([A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]|[^\\x00-\\x7a])*)$", 'gi');
+ regexp = new RegExp("^(?:\\B|[^a-zA-Z0-9_" + atSymbolsWithoutBar + "]|\\s)" + flag + "(?!" + atSymbolsWithBar + ")((?:[A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]|[^\\x00-\\x7a])*)$", 'gi');
match = regexp.exec(subtext);
if (match) {
- return (match[1] || match[1] === "") ? match[1] : match[2];
+ return match[1];
} else {
return null;
}
@@ -103,6 +103,9 @@
this.input.each((i, input) => {
const $input = $(input);
$input.off('focus.setupAtWho').on('focus.setupAtWho', this.setupAtWho.bind(this, $input));
+ // This triggers at.js again
+ // Needed for slash commands with suffixes (ex: /label ~)
+ $input.on('inserted-commands.atwho', $input.trigger.bind($input, 'keyup'));
});
},
setupAtWho: function($input) {
@@ -377,4 +380,4 @@
(dataToInspect === loadingState || dataToInspect.name === loadingState);
}
};
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index d9101b55c7f..a01662e2f9e 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -49,7 +49,10 @@
if (this.options.remote) {
clearTimeout(timeout);
return timeout = setTimeout(function() {
+ $inputContainer.parent().addClass('is-loading');
+
return this.options.query(this.input.val(), function(data) {
+ $inputContainer.parent().removeClass('is-loading');
return this.options.callback(data);
}.bind(this));
}.bind(this), 250);
@@ -437,7 +440,7 @@
}
};
- GitLabDropdown.prototype.opened = function() {
+ GitLabDropdown.prototype.opened = function(e) {
var contentHtml;
this.resetRows();
this.addArrowKeyEvent();
@@ -457,6 +460,10 @@
this.positionMenuAbove();
}
+ if (this.options.opened) {
+ this.options.opened.call(this, e);
+ }
+
return this.dropdown.trigger('shown.gl.dropdown');
};
@@ -840,4 +847,4 @@
}
});
};
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/graphs/graphs_bundle.js b/app/assets/javascripts/graphs/graphs_bundle.js
index 4f7777aa5bc..086dcb34571 100644
--- a/app/assets/javascripts/graphs/graphs_bundle.js
+++ b/app/assets/javascripts/graphs/graphs_bundle.js
@@ -1,3 +1,4 @@
-// require everything else in this directory
-function requireAll(context) { return context.keys().map(context); }
-requireAll(require.context('.', false, /^\.\/(?!graphs_bundle).*\.(js|es6)$/));
+require('./stat_graph_contributors_graph');
+require('./stat_graph_contributors_util');
+require('./stat_graph_contributors');
+require('./stat_graph');
diff --git a/app/assets/javascripts/graphs/stat_graph.js b/app/assets/javascripts/graphs/stat_graph.js
index 2e6da5750de..75a53aae33c 100644
--- a/app/assets/javascripts/graphs/stat_graph.js
+++ b/app/assets/javascripts/graphs/stat_graph.js
@@ -15,4 +15,4 @@
return StatGraph;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors.js b/app/assets/javascripts/graphs/stat_graph_contributors.js
index d06a1a5dae4..bbfb467ad50 100644
--- a/app/assets/javascripts/graphs/stat_graph_contributors.js
+++ b/app/assets/javascripts/graphs/stat_graph_contributors.js
@@ -113,4 +113,4 @@ window.d3 = require('d3');
return ContributorsStatGraph;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js
index 241249fae63..228771da4ee 100644
--- a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js
+++ b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js
@@ -273,4 +273,4 @@ window.d3 = require('d3');
return ContributorsAuthorGraph;
})(ContributorsGraph);
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_util.js b/app/assets/javascripts/graphs/stat_graph_contributors_util.js
index 29c3163328f..7954c583598 100644
--- a/app/assets/javascripts/graphs/stat_graph_contributors_util.js
+++ b/app/assets/javascripts/graphs/stat_graph_contributors_util.js
@@ -135,4 +135,4 @@
}
}
};
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/group_avatar.js b/app/assets/javascripts/group_avatar.js
index 10dfd05fe3c..c5cb273c5b2 100644
--- a/app/assets/javascripts/group_avatar.js
+++ b/app/assets/javascripts/group_avatar.js
@@ -17,4 +17,4 @@
return GroupAvatar;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/groups_select.js b/app/assets/javascripts/groups_select.js
index bc88dc2d092..6b937e7fa0f 100644
--- a/app/assets/javascripts/groups_select.js
+++ b/app/assets/javascripts/groups_select.js
@@ -68,4 +68,4 @@
return GroupsSelect;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/header.js b/app/assets/javascripts/header.js
index fa85f9a6c86..a853c3aeb1f 100644
--- a/app/assets/javascripts/header.js
+++ b/app/assets/javascripts/header.js
@@ -2,7 +2,7 @@
(function() {
$(document).on('todo:toggle', function(e, count) {
var $todoPendingCount = $('.todos-pending-count');
- $todoPendingCount.text(gl.text.addDelimiter(count));
+ $todoPendingCount.text(gl.text.highCountTrim(count));
$todoPendingCount.toggleClass('hidden', count === 0);
});
})();
diff --git a/app/assets/javascripts/importer_status.js b/app/assets/javascripts/importer_status.js
index 9390136d3d8..34e4a257ff9 100644
--- a/app/assets/javascripts/importer_status.js
+++ b/app/assets/javascripts/importer_status.js
@@ -78,4 +78,4 @@
new window.ImporterStatus(jobsImportPath, importPath);
}
});
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/issuable_context.js b/app/assets/javascripts/issuable_context.js
index c77fbb6a1c7..115312d4b83 100644
--- a/app/assets/javascripts/issuable_context.js
+++ b/app/assets/javascripts/issuable_context.js
@@ -76,4 +76,4 @@
return IssuableContext;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js
index 293b856dc4d..de184ab2675 100644
--- a/app/assets/javascripts/issuable_form.js
+++ b/app/assets/javascripts/issuable_form.js
@@ -3,6 +3,8 @@
/* global UsersSelect */
/* global ZenMode */
/* global Autosave */
+/* global dateFormat */
+/* global Pikaday */
(function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
@@ -13,7 +15,7 @@
IssuableForm.prototype.wipRegex = /^\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i;
function IssuableForm(form) {
- var $issuableDueDate;
+ var $issuableDueDate, calendar;
this.form = form;
this.toggleWip = bind(this.toggleWip, this);
this.renderWipExplanation = bind(this.renderWipExplanation, this);
@@ -35,12 +37,15 @@
this.initMoveDropdown();
$issuableDueDate = $('#issuable-due-date');
if ($issuableDueDate.length) {
- $('.datepicker').datepicker({
- dateFormat: 'yy-mm-dd',
- onSelect: function(dateText, inst) {
- return $issuableDueDate.val(dateText);
+ calendar = new Pikaday({
+ field: $issuableDueDate.get(0),
+ theme: 'gitlab-theme',
+ format: 'yyyy-mm-dd',
+ onSelect: function(dateText) {
+ $issuableDueDate.val(dateFormat(new Date(dateText), 'yyyy-mm-dd'));
}
- }).datepicker('setDate', $.datepicker.parseDate('yy-mm-dd', $issuableDueDate.val()));
+ });
+ calendar.setDate(new Date($issuableDueDate.val()));
}
}
@@ -151,4 +156,4 @@
return IssuableForm;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js
index 6c08b1b8e61..52457f70d90 100644
--- a/app/assets/javascripts/issue.js
+++ b/app/assets/javascripts/issue.js
@@ -3,7 +3,7 @@
require('./flash');
require('vendor/jquery.waitforimages');
-require('vendor/task_list');
+require('./task_list');
(function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
@@ -11,10 +11,16 @@ require('vendor/task_list');
this.Issue = (function() {
function Issue() {
this.submitNoteForm = bind(this.submitNoteForm, this);
- // Prevent duplicate event bindings
- this.disableTaskList();
if ($('a.btn-close').length) {
- this.initTaskList();
+ this.taskList = new gl.TaskList({
+ dataType: 'issue',
+ fieldName: 'description',
+ selector: '.detail-page-description',
+ onSuccess: (result) => {
+ document.querySelector('#task_status').innerText = result.task_status;
+ document.querySelector('#task_status_short').innerText = result.task_status_short;
+ }
+ });
this.initIssueBtnEventListeners();
}
this.initMergeRequests();
@@ -22,11 +28,6 @@ require('vendor/task_list');
this.initCanCreateBranch();
}
- Issue.prototype.initTaskList = function() {
- $('.detail-page-description .js-task-list-container').taskList('enable');
- return $(document).on('tasklist:changed', '.detail-page-description .js-task-list-container', this.updateTaskList);
- };
-
Issue.prototype.initIssueBtnEventListeners = function() {
var _this, issueFailMessage;
_this = this;
@@ -54,16 +55,19 @@ require('vendor/task_list');
success: function(data, textStatus, jqXHR) {
if ('id' in data) {
$(document).trigger('issuable:change');
+ const currentTotal = Number($('.issue_counter').text());
if (isClose) {
$('a.btn-close').addClass('hidden');
$('a.btn-reopen').removeClass('hidden');
$('div.status-box-closed').removeClass('hidden');
$('div.status-box-open').addClass('hidden');
+ $('.issue_counter').text(currentTotal - 1);
} else {
$('a.btn-reopen').addClass('hidden');
$('a.btn-close').removeClass('hidden');
$('div.status-box-closed').addClass('hidden');
$('div.status-box-open').removeClass('hidden');
+ $('.issue_counter').text(currentTotal + 1);
}
} else {
new Flash(issueFailMessage, 'alert');
@@ -82,30 +86,6 @@ require('vendor/task_list');
}
};
- Issue.prototype.disableTaskList = function() {
- $('.detail-page-description .js-task-list-container').taskList('disable');
- return $(document).off('tasklist:changed', '.detail-page-description .js-task-list-container');
- };
-
- Issue.prototype.updateTaskList = function() {
- var patchData;
- patchData = {};
- patchData['issue'] = {
- 'description': $('.js-task-list-field', this).val()
- };
- return $.ajax({
- type: 'PATCH',
- url: $('form.js-issuable-update').attr('action'),
- data: patchData,
- success: function(issue) {
- document.querySelector('#task_status').innerText = issue.task_status;
- document.querySelector('#task_status_short').innerText = issue.task_status_short;
- }
- });
- // TODO (rspeicher): Make the issue description inline-editable like a note so
- // that we can re-use its form here
- };
-
Issue.prototype.initMergeRequests = function() {
var $container;
$container = $('#merge-requests');
@@ -152,4 +132,4 @@ require('vendor/task_list');
return Issue;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/issue_status_select.js b/app/assets/javascripts/issue_status_select.js
index 1d6eff11403..b2cfd3ef2a3 100644
--- a/app/assets/javascripts/issue_status_select.js
+++ b/app/assets/javascripts/issue_status_select.js
@@ -31,4 +31,4 @@
return IssueStatusSelect;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/label_manager.js.es6 b/app/assets/javascripts/label_manager.js.es6
index 2a50b72c8aa..38b2eb9ff14 100644
--- a/app/assets/javascripts/label_manager.js.es6
+++ b/app/assets/javascripts/label_manager.js.es6
@@ -1,5 +1,6 @@
/* eslint-disable comma-dangle, class-methods-use-this, no-underscore-dangle, no-param-reassign, no-unused-vars, consistent-return, func-names, space-before-function-paren, max-len */
/* global Flash */
+/* global Sortable */
((global) => {
class LabelManager {
@@ -9,11 +10,12 @@
this.otherLabels = otherLabels || $('.js-other-labels');
this.errorMessage = 'Unable to update label prioritization at this time';
this.emptyState = document.querySelector('#js-priority-labels-empty-state');
- this.prioritizedLabels.sortable({
- items: 'li',
- placeholder: 'list-placeholder',
- axis: 'y',
- update: this.onPrioritySortUpdate.bind(this)
+ this.sortable = Sortable.create(this.prioritizedLabels.get(0), {
+ filter: '.empty-message',
+ forceFallback: true,
+ fallbackClass: 'is-dragging',
+ dataIdAttr: 'data-id',
+ onUpdate: this.onPrioritySortUpdate.bind(this),
});
this.bindEvents();
}
@@ -51,13 +53,13 @@
$target = this.otherLabels;
$from = this.prioritizedLabels;
}
- if ($from.find('li').length === 1) {
+ $label.detach().appendTo($target);
+ if ($from.find('li').length) {
$from.find('.empty-message').removeClass('hidden');
}
- if (!$target.find('li').length) {
+ if ($target.find('> li:not(.empty-message)').length) {
$target.find('.empty-message').addClass('hidden');
}
- $label.detach().appendTo($target);
// Return if we are not persisting state
if (!persistState) {
return;
@@ -101,8 +103,12 @@
getSortedLabelsIds() {
const sortedIds = [];
- this.prioritizedLabels.find('li').each(function() {
- sortedIds.push($(this).data('id'));
+ this.prioritizedLabels.find('> li').each(function() {
+ const id = $(this).data('id');
+
+ if (id) {
+ sortedIds.push(id);
+ }
});
return sortedIds;
}
diff --git a/app/assets/javascripts/labels.js b/app/assets/javascripts/labels.js
index 40ad6fc348e..17a3fc1b1e4 100644
--- a/app/assets/javascripts/labels.js
+++ b/app/assets/javascripts/labels.js
@@ -43,4 +43,4 @@
return Labels;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index e4cf9057e6d..9e2d14c7f87 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -504,4 +504,4 @@
return LabelsSelect;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/layout_nav.js b/app/assets/javascripts/layout_nav.js
index 1c0ea317c1a..08ca9e4fa4d 100644
--- a/app/assets/javascripts/layout_nav.js
+++ b/app/assets/javascripts/layout_nav.js
@@ -44,4 +44,4 @@
}
});
});
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/lib/cropper.js b/app/assets/javascripts/lib/cropper.js
index 5221f85ba7a..7862c6797c3 100644
--- a/app/assets/javascripts/lib/cropper.js
+++ b/app/assets/javascripts/lib/cropper.js
@@ -4,4 +4,4 @@
(function() {
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/lib/raphael.js b/app/assets/javascripts/lib/raphael.js
index 5a9a501efe3..ebe1e2ae98d 100644
--- a/app/assets/javascripts/lib/raphael.js
+++ b/app/assets/javascripts/lib/raphael.js
@@ -6,4 +6,4 @@
(function() {
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/lib/utils/animate.js b/app/assets/javascripts/lib/utils/animate.js
index ce090a2e4fd..d93c1d0da59 100644
--- a/app/assets/javascripts/lib/utils/animate.js
+++ b/app/assets/javascripts/lib/utils/animate.js
@@ -46,4 +46,4 @@
return dfd.promise();
};
})(window);
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/lib/utils/common_utils.js.es6 b/app/assets/javascripts/lib/utils/common_utils.js.es6
index 5becf688652..45a1d90a9d9 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js.es6
+++ b/app/assets/javascripts/lib/utils/common_utils.js.es6
@@ -72,27 +72,18 @@
// This is required to handle non-unicode characters in hash
hash = decodeURIComponent(hash);
- var navbar = document.querySelector('.navbar-gitlab');
- var subnav = document.querySelector('.layout-nav');
- var fixedTabs = document.querySelector('.js-tabs-affix');
-
- var adjustment = 0;
- if (navbar) adjustment -= navbar.offsetHeight;
- if (subnav) adjustment -= subnav.offsetHeight;
-
// scroll to user-generated markdown anchor if we cannot find a match
if (document.getElementById(hash) === null) {
var target = document.getElementById('user-content-' + hash);
if (target && target.scrollIntoView) {
target.scrollIntoView(true);
- window.scrollBy(0, adjustment);
}
} else {
// only adjust for fixedTabs when not targeting user-generated content
+ var fixedTabs = document.querySelector('.js-tabs-affix');
if (fixedTabs) {
- adjustment -= fixedTabs.offsetHeight;
+ window.scrollBy(0, -fixedTabs.offsetHeight);
}
- window.scrollBy(0, adjustment);
}
};
@@ -147,12 +138,10 @@
gl.utils.scrollToElement = function($el) {
var top = $el.offset().top;
- gl.navBarHeight = gl.navBarHeight || $('.navbar-gitlab').height();
- gl.navLinksHeight = gl.navLinksHeight || $('.nav-links').height();
gl.mrTabsHeight = gl.mrTabsHeight || $('.merge-request-tabs').height();
return $('body, html').animate({
- scrollTop: top - (gl.navBarHeight + gl.navLinksHeight + gl.mrTabsHeight)
+ scrollTop: top - (gl.mrTabsHeight)
}, 200);
};
@@ -243,6 +232,21 @@
};
/**
+ * Parses pagination object string values into numbers.
+ *
+ * @param {Object} paginationInformation
+ * @returns {Object}
+ */
+ w.gl.utils.parseIntPagination = paginationInformation => ({
+ perPage: parseInt(paginationInformation['X-PER-PAGE'], 10),
+ page: parseInt(paginationInformation['X-PAGE'], 10),
+ total: parseInt(paginationInformation['X-TOTAL'], 10),
+ totalPages: parseInt(paginationInformation['X-TOTAL-PAGES'], 10),
+ nextPage: parseInt(paginationInformation['X-NEXT-PAGE'], 10),
+ previousPage: parseInt(paginationInformation['X-PREV-PAGE'], 10),
+ });
+
+ /**
* Transforms a DOMStringMap into a plain object.
*
* @param {DOMStringMap} DOMStringMapObject
@@ -252,5 +256,45 @@
acc[element] = DOMStringMapObject[element];
return acc;
}, {});
+
+ /**
+ * Updates the search parameter of a URL given the parameter and values provided.
+ *
+ * If no search params are present we'll add it.
+ * If param for page is already present, we'll update it
+ * If there are params but not for the given one, we'll add it at the end.
+ * Returns the new search parameters.
+ *
+ * @param {String} param
+ * @param {Number|String|Undefined|Null} value
+ * @return {String}
+ */
+ w.gl.utils.setParamInURL = (param, value) => {
+ let search;
+ const locationSearch = window.location.search;
+
+ if (locationSearch.length === 0) {
+ search = `?${param}=${value}`;
+ }
+
+ if (locationSearch.indexOf(param) !== -1) {
+ const regex = new RegExp(param + '=\\d');
+ search = locationSearch.replace(regex, `${param}=${value}`);
+ }
+
+ if (locationSearch.length && locationSearch.indexOf(param) === -1) {
+ search = `${locationSearch}&${param}=${value}`;
+ }
+
+ return search;
+ };
+
+ /**
+ * Converts permission provided as strings to booleans.
+ *
+ * @param {String} string
+ * @returns {Boolean}
+ */
+ w.gl.utils.convertPermissionToBoolean = permission => permission === 'true';
})(window);
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js.es6 b/app/assets/javascripts/lib/utils/datetime_utility.js.es6
index 56300926188..82dcbdc26c8 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js.es6
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js.es6
@@ -2,7 +2,7 @@
/* global timeago */
/* global dateFormat */
-window.timeago = require('vendor/timeago');
+window.timeago = require('timeago.js');
window.dateFormat = require('vendor/date.format');
(function() {
@@ -123,4 +123,4 @@ window.dateFormat = require('vendor/date.format');
return Math.floor((date2 - date1) / millisecondsPerDay);
};
})(window);
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/lib/utils/notify.js b/app/assets/javascripts/lib/utils/notify.js
index 6d5979603b9..66f39122a66 100644
--- a/app/assets/javascripts/lib/utils/notify.js
+++ b/app/assets/javascripts/lib/utils/notify.js
@@ -44,4 +44,4 @@
w.notify = notifyMe;
return w.notifyPermissions = notifyPermissions;
})(window);
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index d9370db0cf2..579d322e3fb 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -1,5 +1,7 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, quotes, one-var, one-var-declaration-per-line, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, no-empty, max-len, consistent-return, no-unused-vars, no-return-assign, max-len */
+require('vendor/latinise');
+
(function() {
(function(w) {
var base;
@@ -12,6 +14,9 @@
gl.text.addDelimiter = function(text) {
return text ? text.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") : text;
};
+ gl.text.highCountTrim = function(count) {
+ return count > 99 ? '99+' : count;
+ };
gl.text.randomString = function() {
return Math.random().toString(36).substring(7);
};
@@ -164,8 +169,14 @@
gl.text.pluralize = function(str, count) {
return str + (count > 1 || count === 0 ? 's' : '');
};
- return gl.text.truncate = function(string, maxLength) {
+ gl.text.truncate = function(string, maxLength) {
return string.substr(0, (maxLength - 3)) + '...';
};
+ gl.text.dasherize = function(str) {
+ return str.replace(/[_\s]+/g, '-');
+ };
+ gl.text.slugify = function(str) {
+ return str.trim().toLowerCase().latinise();
+ };
})(window);
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/lib/utils/type_utility.js b/app/assets/javascripts/lib/utils/type_utility.js
index 6d813d61601..db62e0be324 100644
--- a/app/assets/javascripts/lib/utils/type_utility.js
+++ b/app/assets/javascripts/lib/utils/type_utility.js
@@ -12,4 +12,4 @@
return (obj != null) && (obj.constructor === Object);
};
})(window);
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/lib/utils/url_utility.js.es6 b/app/assets/javascripts/lib/utils/url_utility.js.es6
index a1558b371f0..1bc81d2e4a4 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js.es6
+++ b/app/assets/javascripts/lib/utils/url_utility.js.es6
@@ -83,4 +83,4 @@
document.location.href = url;
};
})(window);
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/line_highlighter.js b/app/assets/javascripts/line_highlighter.js
index d7137ec63e4..966fcd8ec47 100644
--- a/app/assets/javascripts/line_highlighter.js
+++ b/app/assets/javascripts/line_highlighter.js
@@ -179,4 +179,4 @@ require('vendor/jquery.scrollTo');
return LineHighlighter;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/logo.js b/app/assets/javascripts/logo.js
index 1b0d0768db8..729baa2e1a7 100644
--- a/app/assets/javascripts/logo.js
+++ b/app/assets/javascripts/logo.js
@@ -4,4 +4,4 @@
window.addEventListener('beforeunload', function() {
$('.tanuki-logo').addClass('animate');
});
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/member_expiration_date.js.es6 b/app/assets/javascripts/member_expiration_date.js.es6
index bf6c0ec2798..129d2dc5f0a 100644
--- a/app/assets/javascripts/member_expiration_date.js.es6
+++ b/app/assets/javascripts/member_expiration_date.js.es6
@@ -1,3 +1,5 @@
+/* global Pikaday */
+/* global dateFormat */
(() => {
// Add datepickers to all `js-access-expiration-date` elements. If those elements are
// children of an element with the `clearable-input` class, and have a sibling
@@ -11,21 +13,35 @@
}
const inputs = $(selector);
- inputs.datepicker({
- dateFormat: 'yy-mm-dd',
- minDate: 1,
- onSelect: function onSelect() {
- $(this).trigger('change');
- toggleClearInput.call(this);
- },
+ inputs.each((i, el) => {
+ const $input = $(el);
+
+ const calendar = new Pikaday({
+ field: $input.get(0),
+ theme: 'gitlab-theme',
+ format: 'yyyy-mm-dd',
+ minDate: new Date(),
+ onSelect(dateText) {
+ $input.val(dateFormat(new Date(dateText), 'yyyy-mm-dd'));
+
+ $input.trigger('change');
+
+ toggleClearInput.call($input);
+ },
+ });
+
+ calendar.setDate(new Date($input.val()));
+ $input.data('pikaday', calendar);
});
inputs.next('.js-clear-input').on('click', function clicked(event) {
event.preventDefault();
const input = $(this).closest('.clearable-input').find(selector);
- input.datepicker('setDate', null)
- .trigger('change');
+ const calendar = input.data('pikaday');
+
+ calendar.setDate(null);
+ input.trigger('change');
toggleClearInput.call(input);
});
@@ -33,4 +49,4 @@
inputs.each(toggleClearInput);
};
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js
index 8762ec35b80..5e01aacf2ba 100644
--- a/app/assets/javascripts/merge_request.js
+++ b/app/assets/javascripts/merge_request.js
@@ -2,7 +2,7 @@
/* global MergeRequestTabs */
require('vendor/jquery.waitforimages');
-require('vendor/task_list');
+require('./task_list');
require('./merge_request_tabs');
(function() {
@@ -24,12 +24,18 @@ require('./merge_request_tabs');
};
})(this));
this.initTabs();
- // Prevent duplicate event bindings
- this.disableTaskList();
this.initMRBtnListeners();
this.initCommitMessageListeners();
if ($("a.btn-close").length) {
- this.initTaskList();
+ this.taskList = new gl.TaskList({
+ dataType: 'merge_request',
+ fieldName: 'description',
+ selector: '.detail-page-description',
+ onSuccess: (result) => {
+ document.querySelector('#task_status').innerText = result.task_status;
+ document.querySelector('#task_status_short').innerText = result.task_status_short;
+ }
+ });
}
}
@@ -50,11 +56,6 @@ require('./merge_request_tabs');
return this.$('.all-commits').removeClass('hide');
};
- MergeRequest.prototype.initTaskList = function() {
- $('.detail-page-description .js-task-list-container').taskList('enable');
- return $(document).on('tasklist:changed', '.detail-page-description .js-task-list-container', this.updateTaskList);
- };
-
MergeRequest.prototype.initMRBtnListeners = function() {
var _this;
_this = this;
@@ -85,38 +86,14 @@ require('./merge_request_tabs');
}
};
- MergeRequest.prototype.disableTaskList = function() {
- $('.detail-page-description .js-task-list-container').taskList('disable');
- return $(document).off('tasklist:changed', '.detail-page-description .js-task-list-container');
- };
-
- MergeRequest.prototype.updateTaskList = function() {
- var patchData;
- patchData = {};
- patchData['merge_request'] = {
- 'description': $('.js-task-list-field', this).val()
- };
- return $.ajax({
- type: 'PATCH',
- url: $('form.js-issuable-update').attr('action'),
- data: patchData,
- success: function(mergeRequest) {
- document.querySelector('#task_status').innerText = mergeRequest.task_status;
- document.querySelector('#task_status_short').innerText = mergeRequest.task_status_short;
- }
- });
- // TODO (rspeicher): Make the merge request description inline-editable like a
- // note so that we can re-use its form here
- };
-
MergeRequest.prototype.initCommitMessageListeners = function() {
$(document).on('click', 'a.js-with-description-link', function(e) {
var textarea = $('textarea.js-commit-message');
e.preventDefault();
textarea.val(textarea.data('messageWithDescription'));
- $('p.js-with-description-hint').hide();
- $('p.js-without-description-hint').show();
+ $('.js-with-description-hint').hide();
+ $('.js-without-description-hint').show();
});
$(document).on('click', 'a.js-without-description-link', function(e) {
@@ -124,11 +101,11 @@ require('./merge_request_tabs');
e.preventDefault();
textarea.val(textarea.data('messageWithoutDescription'));
- $('p.js-with-description-hint').show();
- $('p.js-without-description-hint').hide();
+ $('.js-with-description-hint').show();
+ $('.js-without-description-hint').hide();
});
};
return MergeRequest;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/merge_request_tabs.js.es6 b/app/assets/javascripts/merge_request_tabs.js.es6
index af1ba9ecaf3..190336dbd20 100644
--- a/app/assets/javascripts/merge_request_tabs.js.es6
+++ b/app/assets/javascripts/merge_request_tabs.js.es6
@@ -4,7 +4,7 @@
/* global Flash */
require('./breakpoints');
-window.Cookies = require('vendor/js.cookie');
+window.Cookies = require('js-cookie');
require('./flash');
/* eslint-disable max-len */
@@ -61,6 +61,7 @@ require('./flash');
constructor({ action, setUrl, stubLocation } = {}) {
this.diffsLoaded = false;
+ this.pipelinesLoaded = false;
this.commitsLoaded = false;
this.fixedLayoutPref = null;
@@ -102,9 +103,10 @@ require('./flash');
}
clickTab(e) {
- if (e.target && gl.utils.isMetaClick(e)) {
- const targetLink = e.target.getAttribute('href');
+ if (e.currentTarget && gl.utils.isMetaClick(e)) {
+ const targetLink = e.currentTarget.getAttribute('href');
e.stopImmediatePropagation();
+ e.preventDefault();
window.open(targetLink, '_blank');
}
}
@@ -125,10 +127,16 @@ require('./flash');
if (this.diffViewType() === 'parallel') {
this.expandViewContainer();
}
- const navBarHeight = $('.navbar-gitlab').outerHeight();
$.scrollTo('.merge-request-details .merge-request-tabs', {
- offset: -navBarHeight,
+ offset: 0,
});
+ } else if (action === 'pipelines') {
+ if (this.pipelinesLoaded) {
+ return;
+ }
+ const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
+ gl.commits.pipelines.PipelinesTableBundle.$mount(pipelineTableViewEl);
+ this.pipelinesLoaded = true;
} else {
this.expandView();
this.resetViewContainer();
@@ -140,11 +148,7 @@ require('./flash');
scrollToElement(container) {
if (location.hash) {
- const offset = 0 - (
- $('.navbar-gitlab').outerHeight() +
- $('.layout-nav').outerHeight() +
- $('.js-tabs-affix').outerHeight()
- );
+ const offset = -$('.js-tabs-affix').outerHeight();
const $el = $(`${container} ${location.hash}:not(.match)`);
if ($el.length) {
$.scrollTo($el[0], { offset });
@@ -330,14 +334,12 @@ require('./flash');
if (Breakpoints.get().getBreakpointSize() === 'xs' || !$tabs.length) return;
const $diffTabs = $('#diff-notes-app');
- const $fixedNav = $('.navbar-fixed-top');
- const $layoutNav = $('.layout-nav');
$tabs.off('affix.bs.affix affix-top.bs.affix')
.affix({
offset: {
top: () => (
- $diffTabs.offset().top - $tabs.height() - $fixedNav.height() - $layoutNav.height()
+ $diffTabs.offset().top - $tabs.height()
),
},
})
diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6
index e5d2d706fc7..88f08bbaa34 100644
--- a/app/assets/javascripts/merge_request_widget.js.es6
+++ b/app/assets/javascripts/merge_request_widget.js.es6
@@ -110,7 +110,7 @@ require('./smart_interval');
urlSuffix = deleteSourceBranch ? '?deleted_source_branch=true' : '';
return window.location.href = window.location.pathname + urlSuffix;
} else if (data.merge_error) {
- return _this.$widgetBody.html("<h4>" + data.merge_error + "</h4>");
+ return $('.mr-widget-body').html("<h4>" + data.merge_error + "</h4>");
} else {
callback = function() {
return merge_request_widget.mergeInProgress(deleteSourceBranch);
@@ -154,7 +154,7 @@ require('./smart_interval');
return $.getJSON(this.opts.ci_status_url, (function(_this) {
return function(data) {
var message, status, title;
- if (data.status === '') {
+ if (!data.status) {
return;
}
if (data.environments && data.environments.length) _this.renderEnvironments(data.environments);
@@ -252,7 +252,6 @@ require('./smart_interval');
$('.ci_widget.ci-error').show();
this.setMergeButtonClass('btn-danger');
}
- this.initMiniPipelineGraph();
};
MergeRequestWidget.prototype.showCICoverage = function(coverage) {
diff --git a/app/assets/javascripts/merged_buttons.js b/app/assets/javascripts/merged_buttons.js
index 527cdc9b698..9548a98f499 100644
--- a/app/assets/javascripts/merged_buttons.js
+++ b/app/assets/javascripts/merged_buttons.js
@@ -42,4 +42,4 @@
return MergedButtons;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/milestone.js b/app/assets/javascripts/milestone.js
index 7ce1259e015..7fbaeec7882 100644
--- a/app/assets/javascripts/milestone.js
+++ b/app/assets/javascripts/milestone.js
@@ -1,5 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-use-before-define, camelcase, quotes, object-shorthand, no-shadow, no-unused-vars, comma-dangle, no-var, prefer-template, no-underscore-dangle, consistent-return, one-var, one-var-declaration-per-line, default-case, prefer-arrow-callback, max-len */
/* global Flash */
+/* global Sortable */
(function() {
this.Milestone = (function() {
@@ -8,11 +9,9 @@
type: "PUT",
url: issue_url,
data: data,
- success: (function(_this) {
- return function(_data) {
- return _this.successCallback(_data, li);
- };
- })(this),
+ success: function(_data) {
+ return Milestone.successCallback(_data, li);
+ },
error: function(data) {
return new Flash("Issue update failed", 'alert');
},
@@ -27,11 +26,9 @@
type: "PUT",
url: sort_issues_url,
data: data,
- success: (function(_this) {
- return function(_data) {
- return _this.successCallback(_data);
- };
- })(this),
+ success: function(_data) {
+ return Milestone.successCallback(_data);
+ },
error: function() {
return new Flash("Issues update failed", 'alert');
},
@@ -46,11 +43,9 @@
type: "PUT",
url: sort_mr_url,
data: data,
- success: (function(_this) {
- return function(_data) {
- return _this.successCallback(_data);
- };
- })(this),
+ success: function(_data) {
+ return Milestone.successCallback(_data);
+ },
error: function(data) {
return new Flash("Issue update failed", 'alert');
},
@@ -63,11 +58,9 @@
type: "PUT",
url: merge_request_url,
data: data,
- success: (function(_this) {
- return function(_data) {
- return _this.successCallback(_data, li);
- };
- })(this),
+ success: function(_data) {
+ return Milestone.successCallback(_data, li);
+ },
error: function(data) {
return new Flash("Issue update failed", 'alert');
},
@@ -81,65 +74,30 @@
img_tag = $('<img/>');
img_tag.attr('src', data.assignee.avatar_url);
img_tag.addClass('avatar s16');
- $(element).find('.assignee-icon').html(img_tag);
+ $(element).find('.assignee-icon img').replaceWith(img_tag);
} else {
- $(element).find('.assignee-icon').html('');
+ $(element).find('.assignee-icon').empty();
}
return $(element).effect('highlight');
};
function Milestone() {
var oldMouseStart;
- oldMouseStart = $.ui.sortable.prototype._mouseStart;
- $.ui.sortable.prototype._mouseStart = function(event, overrideHandle, noActivation) {
- this._trigger("beforeStart", event, this._uiHash());
- return oldMouseStart.apply(this, [event, overrideHandle, noActivation]);
- };
this.bindIssuesSorting();
this.bindMergeRequestSorting();
this.bindTabsSwitching();
}
Milestone.prototype.bindIssuesSorting = function() {
- return $("#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed").sortable({
- connectWith: ".issues-sortable-list",
- dropOnEmpty: true,
- items: "li:not(.ui-sort-disabled)",
- beforeStart: function(event, ui) {
- return $(".issues-sortable-list").css("min-height", ui.item.outerHeight());
- },
- stop: function(event, ui) {
- return $(".issues-sortable-list").css("min-height", "0px");
- },
- update: function(event, ui) {
- var data;
- // Prevents sorting from container which element has been removed.
- if ($(this).find(ui.item).length > 0) {
- data = $(this).sortable("serialize");
- return Milestone.sortIssues(data);
- }
- },
- receive: function(event, ui) {
- var data, issue_id, issue_url, new_state;
- new_state = $(this).data('state');
- issue_id = ui.item.data('iid');
- issue_url = ui.item.data('url');
- data = (function() {
- switch (new_state) {
- case 'ongoing':
- return "issue[assignee_id]=" + gon.current_user_id;
- case 'unassigned':
- return "issue[assignee_id]=";
- case 'closed':
- return "issue[state_event]=close";
- }
- })();
- if ($(ui.sender).data('state') === "closed") {
- data += "&issue[state_event]=reopen";
- }
- return Milestone.updateIssue(ui.item, issue_url, data);
- }
- }).disableSelection();
+ $('#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed').each(function (i, el) {
+ this.createSortable(el, {
+ group: 'issue-list',
+ listEls: $('.issues-sortable-list'),
+ fieldName: 'issue',
+ sortCallback: Milestone.sortIssues,
+ updateCallback: Milestone.updateIssue,
+ });
+ }.bind(this));
};
Milestone.prototype.bindTabsSwitching = function() {
@@ -154,44 +112,64 @@
};
Milestone.prototype.bindMergeRequestSorting = function() {
- return $("#merge_requests-list-unassigned, #merge_requests-list-ongoing, #merge_requests-list-closed").sortable({
- connectWith: ".merge_requests-sortable-list",
- dropOnEmpty: true,
- items: "li:not(.ui-sort-disabled)",
- beforeStart: function(event, ui) {
- return $(".merge_requests-sortable-list").css("min-height", ui.item.outerHeight());
+ $("#merge_requests-list-unassigned, #merge_requests-list-ongoing, #merge_requests-list-closed").each(function (i, el) {
+ this.createSortable(el, {
+ group: 'merge-request-list',
+ listEls: $(".merge_requests-sortable-list:not(#merge_requests-list-merged)"),
+ fieldName: 'merge_request',
+ sortCallback: Milestone.sortMergeRequests,
+ updateCallback: Milestone.updateMergeRequest,
+ });
+ }.bind(this));
+ };
+
+ Milestone.prototype.createSortable = function(el, opts) {
+ return Sortable.create(el, {
+ group: opts.group,
+ filter: '.is-disabled',
+ forceFallback: true,
+ onStart: function(e) {
+ opts.listEls.css('min-height', e.item.offsetHeight);
},
- stop: function(event, ui) {
- return $(".merge_requests-sortable-list").css("min-height", "0px");
+ onEnd: function () {
+ opts.listEls.css("min-height", "0px");
},
- update: function(event, ui) {
- var data;
- data = $(this).sortable("serialize");
- return Milestone.sortMergeRequests(data);
+ onUpdate: function(e) {
+ var ids = this.toArray(),
+ data;
+
+ if (ids.length) {
+ data = ids.map(function(id) {
+ return 'sortable_' + opts.fieldName + '[]=' + id;
+ }).join('&');
+
+ opts.sortCallback(data);
+ }
},
- receive: function(event, ui) {
- var data, merge_request_id, merge_request_url, new_state;
- new_state = $(this).data('state');
- merge_request_id = ui.item.data('iid');
- merge_request_url = ui.item.data('url');
+ onAdd: function (e) {
+ var data, issuableId, issuableUrl, newState;
+ newState = e.to.dataset.state;
+ issuableUrl = e.item.dataset.url;
data = (function() {
- switch (new_state) {
+ switch (newState) {
case 'ongoing':
- return "merge_request[assignee_id]=" + gon.current_user_id;
+ return opts.fieldName + '[assignee_id]=' + gon.current_user_id;
case 'unassigned':
- return "merge_request[assignee_id]=";
+ return opts.fieldName + '[assignee_id]=';
case 'closed':
- return "merge_request[state_event]=close";
+ return opts.fieldName + '[state_event]=close';
}
})();
- if ($(ui.sender).data('state') === "closed") {
- data += "&merge_request[state_event]=reopen";
+ if (e.from.dataset.state === 'closed') {
+ data += '&' + opts.fieldName + '[state_event]=reopen';
}
- return Milestone.updateMergeRequest(ui.item, merge_request_url, data);
+
+ opts.updateCallback(e.item, issuableUrl, data);
+ this.options.onUpdate.call(this, e);
}
- }).disableSelection();
+ });
};
return Milestone;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
index 2f08aa7fe8b..51fa5c828b3 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -39,7 +39,7 @@
$value = $block.find('.value');
$loading = $block.find('.block-loading').fadeOut();
if (issueUpdateURL) {
- milestoneLinkTemplate = _.template('<a href="/<%- namespace %>/<%- path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>');
+ milestoneLinkTemplate = _.template('<a href="/<%- full_path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>');
milestoneLinkNoneTemplate = '<span class="no-value">None</span>';
collapsedSidebarLabelTemplate = _.template('<span class="has-tooltip" data-container="body" title="<%- remaining %>" data-placement="left"> <%- title %> </span>');
}
@@ -181,8 +181,7 @@
$selectbox.hide();
$value.css('display', '');
if (data.milestone != null) {
- data.milestone.namespace = _this.currentProject.namespace;
- data.milestone.path = _this.currentProject.path;
+ data.milestone.full_path = _this.currentProject.full_path;
data.milestone.remaining = gl.utils.timeFor(data.milestone.due_date);
$value.html(milestoneLinkTemplate(data.milestone));
return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone));
@@ -199,4 +198,4 @@
return MilestoneSelect;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/mini_pipeline_graph_dropdown.js.es6 b/app/assets/javascripts/mini_pipeline_graph_dropdown.js.es6
index 4becbc32681..2145e531331 100644
--- a/app/assets/javascripts/mini_pipeline_graph_dropdown.js.es6
+++ b/app/assets/javascripts/mini_pipeline_graph_dropdown.js.es6
@@ -28,7 +28,7 @@
* All dropdown events are fired at the .dropdown-menu's parent element.
*/
bindEvents() {
- $(this.container).on('shown.bs.dropdown', this.getBuildsList);
+ $(document).off('shown.bs.dropdown', this.container).on('shown.bs.dropdown', this.container, this.getBuildsList);
}
/**
diff --git a/app/assets/javascripts/namespace_select.js b/app/assets/javascripts/namespace_select.js
index 514556ade0b..b98e6121967 100644
--- a/app/assets/javascripts/namespace_select.js
+++ b/app/assets/javascripts/namespace_select.js
@@ -29,7 +29,7 @@
if (selected.id == null) {
return selected.text;
} else {
- return selected.kind + ": " + selected.path;
+ return selected.kind + ": " + selected.full_path;
}
},
data: function(term, dataCallback) {
@@ -50,7 +50,7 @@
if (namespace.id == null) {
return namespace.text;
} else {
- return namespace.kind + ": " + namespace.path;
+ return namespace.kind + ": " + namespace.full_path;
}
},
renderRow: this.renderRow,
@@ -83,4 +83,4 @@
return NamespaceSelects;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/network/branch_graph.js b/app/assets/javascripts/network/branch_graph.js
index a7ccd03b60c..43dc9838977 100644
--- a/app/assets/javascripts/network/branch_graph.js
+++ b/app/assets/javascripts/network/branch_graph.js
@@ -421,4 +421,4 @@
y: h
});
};
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/network/network.js b/app/assets/javascripts/network/network.js
index 37bf6436fd1..8e7027b44e7 100644
--- a/app/assets/javascripts/network/network.js
+++ b/app/assets/javascripts/network/network.js
@@ -17,4 +17,4 @@
return Network;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/network/network_bundle.js b/app/assets/javascripts/network/network_bundle.js
index b4491354472..e5947586583 100644
--- a/app/assets/javascripts/network/network_bundle.js
+++ b/app/assets/javascripts/network/network_bundle.js
@@ -2,9 +2,8 @@
/* global Network */
/* global ShortcutsNetwork */
-// require everything else in this directory
-function requireAll(context) { return context.keys().map(context); }
-requireAll(require.context('.', false, /^\.\/(?!network_bundle).*\.(js|es6)$/));
+require('./branch_graph');
+require('./network');
(function() {
$(function() {
@@ -19,4 +18,4 @@ requireAll(require.context('.', false, /^\.\/(?!network_bundle).*\.(js|es6)$/));
});
return new ShortcutsNetwork(network_graph.branch_graph);
});
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/new_branch_form.js b/app/assets/javascripts/new_branch_form.js
index 7f763c13b50..cb24f212c66 100644
--- a/app/assets/javascripts/new_branch_form.js
+++ b/app/assets/javascripts/new_branch_form.js
@@ -100,4 +100,4 @@
return NewBranchForm;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/new_commit_form.js b/app/assets/javascripts/new_commit_form.js
index 41eea78a3e6..747f693726e 100644
--- a/app/assets/javascripts/new_commit_form.js
+++ b/app/assets/javascripts/new_commit_form.js
@@ -30,4 +30,4 @@
return NewCommitForm;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index d108da29af7..03504255bda 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -11,7 +11,7 @@ require('./dropzone_input');
require('./gfm_auto_complete');
require('vendor/jquery.caret'); // required by jquery.atwho
require('vendor/jquery.atwho');
-require('vendor/task_list');
+require('./task_list');
(function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
@@ -51,7 +51,11 @@ require('vendor/task_list');
this.addBinding();
this.setPollingInterval();
this.setupMainTargetNoteForm();
- this.initTaskList();
+ this.taskList = new gl.TaskList({
+ dataType: 'note',
+ fieldName: 'note',
+ selector: '.notes'
+ });
this.collapseLongCommitList();
// We are in the Merge Requests page so we need another edit form for Changes tab
@@ -125,8 +129,6 @@ require('vendor/task_list');
$(document).off("keydown", ".js-note-text");
$(document).off('click', '.js-comment-resolve-button');
$(document).off("click", '.system-note-commit-list-toggler');
- $('.note .js-task-list-container').taskList('disable');
- return $(document).off('tasklist:changed', '.note .js-task-list-container');
};
Notes.prototype.keydownNoteText = function(e) {
@@ -286,7 +288,7 @@ require('vendor/task_list');
// Update datetime format on the recent note
gl.utils.localTimeAgo($notesList.find("#note_" + note.id + " .js-timeago"), false);
this.collapseLongCommitList();
- this.initTaskList();
+ this.taskList.init();
this.refresh();
return this.updateNotesCount(1);
}
@@ -455,7 +457,7 @@ require('vendor/task_list');
var mergeRequestId = $form.data('noteable-iid');
if (ResolveService != null) {
- ResolveService.toggleResolveForDiscussion(projectPath, mergeRequestId, discussionId);
+ ResolveService.toggleResolveForDiscussion(mergeRequestId, discussionId);
}
}
@@ -863,15 +865,6 @@ require('vendor/task_list');
}
};
- Notes.prototype.initTaskList = function() {
- this.enableTaskList();
- return $(document).on('tasklist:changed', '.note .js-task-list-container', this.updateTaskList.bind(this));
- };
-
- Notes.prototype.enableTaskList = function() {
- return $('.note .js-task-list-container').taskList('enable');
- };
-
Notes.prototype.putEditFormInPlace = function($el) {
var $editForm = $(this.getEditFormSelector($el));
var $note = $el.closest('.note');
@@ -896,17 +889,6 @@ require('vendor/task_list');
$editForm.find('.referenced-users').hide();
};
- Notes.prototype.updateTaskList = function(e) {
- var $target = $(e.target);
- var $list = $target.closest('.js-task-list-container');
- var $editForm = $(this.getEditFormSelector($target));
- var $note = $list.closest('.note');
-
- this.putEditFormInPlace($list);
- $editForm.find('#note_note').val($note.find('.original-task-list').val());
- $('form', $list).submit();
- };
-
Notes.prototype.updateNotesCount = function(updateCount) {
return this.notesCountBadge.text(parseInt(this.notesCountBadge.text(), 10) + updateCount);
};
@@ -923,9 +905,10 @@ require('vendor/task_list');
};
Notes.prototype.toggleCommitList = function(e) {
- const $element = $(e.target);
+ const $element = $(e.currentTarget);
const $closestSystemCommitList = $element.siblings('.system-note-commit-list');
+ $element.find('.fa').toggleClass('fa-angle-down').toggleClass('fa-angle-up');
$closestSystemCommitList.toggleClass('hide-shade');
};
@@ -954,4 +937,4 @@ require('vendor/task_list');
return Notes;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/notifications_dropdown.js b/app/assets/javascripts/notifications_dropdown.js
index 926dc35fee8..838356133cd 100644
--- a/app/assets/javascripts/notifications_dropdown.js
+++ b/app/assets/javascripts/notifications_dropdown.js
@@ -28,4 +28,4 @@
return NotificationsDropdown;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/notifications_form.js b/app/assets/javascripts/notifications_form.js
index c3d7cc0adfb..5005af90d48 100644
--- a/app/assets/javascripts/notifications_form.js
+++ b/app/assets/javascripts/notifications_form.js
@@ -54,4 +54,4 @@
return NotificationsForm;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/profile/profile.js.es6 b/app/assets/javascripts/profile/profile.js.es6
index 5aec9c813fe..81374296522 100644
--- a/app/assets/javascripts/profile/profile.js.es6
+++ b/app/assets/javascripts/profile/profile.js.es6
@@ -25,6 +25,7 @@
bindEvents() {
$('.js-preferences-form').on('change.preference', 'input[type=radio]', this.submitForm);
$('#user_notification_email').on('change', this.submitForm);
+ $('#user_notified_of_own_activity').on('change', this.submitForm);
$('.update-username').on('ajax:before', this.beforeUpdateUsername);
$('.update-username').on('ajax:complete', this.afterUpdateUsername);
$('.update-notifications').on('ajax:success', this.onUpdateNotifs);
diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js
index 71719917d0c..7c03c8b72d4 100644
--- a/app/assets/javascripts/project.js
+++ b/app/assets/javascripts/project.js
@@ -126,4 +126,4 @@
return Project;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/project_avatar.js b/app/assets/javascripts/project_avatar.js
index a6d3ba9eb86..aabdfbf65e2 100644
--- a/app/assets/javascripts/project_avatar.js
+++ b/app/assets/javascripts/project_avatar.js
@@ -17,4 +17,4 @@
return ProjectAvatar;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js
index 04fe84683f3..e01668eabef 100644
--- a/app/assets/javascripts/project_find_file.js
+++ b/app/assets/javascripts/project_find_file.js
@@ -168,4 +168,4 @@
return ProjectFindFile;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/project_fork.js b/app/assets/javascripts/project_fork.js
index 208f25a0e33..47197db39d3 100644
--- a/app/assets/javascripts/project_fork.js
+++ b/app/assets/javascripts/project_fork.js
@@ -10,4 +10,4 @@
return ProjectFork;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/project_import.js b/app/assets/javascripts/project_import.js
index d7943959238..08334bf1ec5 100644
--- a/app/assets/javascripts/project_import.js
+++ b/app/assets/javascripts/project_import.js
@@ -10,4 +10,4 @@
return ProjectImport;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/project_label_subscription.js.es6 b/app/assets/javascripts/project_label_subscription.js.es6
index 8365f7118d5..0a811627600 100644
--- a/app/assets/javascripts/project_label_subscription.js.es6
+++ b/app/assets/javascripts/project_label_subscription.js.es6
@@ -38,13 +38,15 @@
this.$buttons.attr('data-status', newStatus);
this.$buttons.find('> span').text(newAction);
- for (const button of this.$buttons) {
+ this.$buttons.map((button) => {
const $button = $(button);
if ($button.attr('data-original-title')) {
$button.tooltip('hide').attr('data-original-title', newAction).tooltip('fixTitle');
}
- }
+
+ return button;
+ });
});
}
}
diff --git a/app/assets/javascripts/project_new.js b/app/assets/javascripts/project_new.js
index 3aa6f6771ce..e9927c1bf51 100644
--- a/app/assets/javascripts/project_new.js
+++ b/app/assets/javascripts/project_new.js
@@ -101,4 +101,4 @@
return ProjectNew;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js
index 7b5e9953598..f80e765ce30 100644
--- a/app/assets/javascripts/project_select.js
+++ b/app/assets/javascripts/project_select.js
@@ -101,4 +101,4 @@
return ProjectSelect;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/project_show.js b/app/assets/javascripts/project_show.js
index aad130cf267..3a51c1f26ac 100644
--- a/app/assets/javascripts/project_show.js
+++ b/app/assets/javascripts/project_show.js
@@ -6,6 +6,6 @@
return ProjectShow;
})();
-}).call(this);
+}).call(window);
// I kept class for future
diff --git a/app/assets/javascripts/projects_list.js b/app/assets/javascripts/projects_list.js
index 69a11dfaf39..acdf9b7eb5a 100644
--- a/app/assets/javascripts/projects_list.js
+++ b/app/assets/javascripts/projects_list.js
@@ -47,4 +47,4 @@
});
}
};
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/render_gfm.js b/app/assets/javascripts/render_gfm.js
index bdbad93ad04..48cae8a4fa9 100644
--- a/app/assets/javascripts/render_gfm.js
+++ b/app/assets/javascripts/render_gfm.js
@@ -12,4 +12,4 @@
$(document).on('ready load', function() {
return $('body').renderGFM();
});
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/render_math.js b/app/assets/javascripts/render_math.js
index 6cef449babf..76c61c001ba 100644
--- a/app/assets/javascripts/render_math.js
+++ b/app/assets/javascripts/render_math.js
@@ -51,4 +51,4 @@
});
}
};
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js
index 76a0f993ea0..903862cac6b 100644
--- a/app/assets/javascripts/right_sidebar.js
+++ b/app/assets/javascripts/right_sidebar.js
@@ -21,11 +21,16 @@
};
Sidebar.prototype.addEventListeners = function() {
+ const $document = $(document);
+ const throttledSetSidebarHeight = _.throttle(this.setSidebarHeight, 10);
+
this.sidebar.on('click', '.sidebar-collapsed-icon', this, this.sidebarCollapseClicked);
$('.dropdown').on('hidden.gl.dropdown', this, this.onSidebarDropdownHidden);
$('.dropdown').on('loading.gl.dropdown', this.sidebarDropdownLoading);
$('.dropdown').on('loaded.gl.dropdown', this.sidebarDropdownLoaded);
- $(document).on('click', '.js-sidebar-toggle', function(e, triggered) {
+ $(window).on('resize', () => throttledSetSidebarHeight());
+ $document.on('scroll', () => throttledSetSidebarHeight());
+ $document.on('click', '.js-sidebar-toggle', function(e, triggered) {
var $allGutterToggleIcons, $this, $thisIcon;
e.preventDefault();
$this = $(this);
@@ -191,6 +196,17 @@
}
};
+ Sidebar.prototype.setSidebarHeight = function() {
+ const $navHeight = $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight();
+ const $rightSidebar = $('.js-right-sidebar');
+ const diff = $navHeight - $('body').scrollTop();
+ if (diff > 0) {
+ $rightSidebar.outerHeight($(window).height() - diff);
+ } else {
+ $rightSidebar.outerHeight('100%');
+ }
+ };
+
Sidebar.prototype.isOpen = function() {
return this.sidebar.is('.right-sidebar-expanded');
};
@@ -201,4 +217,4 @@
return Sidebar;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/search.js b/app/assets/javascripts/search.js
index b1c0dc37b4d..e66418beeab 100644
--- a/app/assets/javascripts/search.js
+++ b/app/assets/javascripts/search.js
@@ -97,4 +97,4 @@
return Search;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/search_autocomplete.js.es6 b/app/assets/javascripts/search_autocomplete.js.es6
index 6250e75d407..6fd5345a0a6 100644
--- a/app/assets/javascripts/search_autocomplete.js.es6
+++ b/app/assets/javascripts/search_autocomplete.js.es6
@@ -169,10 +169,10 @@
url: issuesPath + "/?author_username=" + userName
}, 'separator', {
text: 'Merge requests assigned to me',
- url: mrPath + "/?assignee_id=" + userId
+ url: mrPath + "/?assignee_username=" + userName
}, {
text: "Merge requests I've created",
- url: mrPath + "/?author_id=" + userId
+ url: mrPath + "/?author_username=" + userName
}
];
if (!name) {
diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/shortcuts.js
index c6d9b007ad1..81766f4bd55 100644
--- a/app/assets/javascripts/shortcuts.js
+++ b/app/assets/javascripts/shortcuts.js
@@ -97,4 +97,4 @@
}
};
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/shortcuts_dashboard_navigation.js b/app/assets/javascripts/shortcuts_dashboard_navigation.js
index 7378b322426..e7baea894f6 100644
--- a/app/assets/javascripts/shortcuts_dashboard_navigation.js
+++ b/app/assets/javascripts/shortcuts_dashboard_navigation.js
@@ -37,4 +37,4 @@ require('./shortcuts');
return ShortcutsDashboardNavigation;
})(Shortcuts);
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/shortcuts_find_file.js b/app/assets/javascripts/shortcuts_find_file.js
index 36e379d634d..a27ac264a5c 100644
--- a/app/assets/javascripts/shortcuts_find_file.js
+++ b/app/assets/javascripts/shortcuts_find_file.js
@@ -35,4 +35,4 @@ require('./shortcuts_navigation');
return ShortcutsFindFile;
})(ShortcutsNavigation);
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/shortcuts_issuable.js b/app/assets/javascripts/shortcuts_issuable.js
index b841abb754d..fe58e98cee5 100644
--- a/app/assets/javascripts/shortcuts_issuable.js
+++ b/app/assets/javascripts/shortcuts_issuable.js
@@ -89,4 +89,4 @@ require('./shortcuts_navigation');
return ShortcutsIssuable;
})(ShortcutsNavigation);
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/shortcuts_navigation.js b/app/assets/javascripts/shortcuts_navigation.js
index cb5f2c53ea6..542cd586df0 100644
--- a/app/assets/javascripts/shortcuts_navigation.js
+++ b/app/assets/javascripts/shortcuts_navigation.js
@@ -65,4 +65,4 @@ require('./shortcuts');
return ShortcutsNavigation;
})(Shortcuts);
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/shortcuts_network.js b/app/assets/javascripts/shortcuts_network.js
index 651957f5325..4c2bf8bf001 100644
--- a/app/assets/javascripts/shortcuts_network.js
+++ b/app/assets/javascripts/shortcuts_network.js
@@ -25,4 +25,4 @@ require('./shortcuts_navigation');
return ShortcutsNetwork;
})(ShortcutsNavigation);
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/sidebar.js.es6 b/app/assets/javascripts/sidebar.js.es6
deleted file mode 100644
index cbb2ae9f1bd..00000000000
--- a/app/assets/javascripts/sidebar.js.es6
+++ /dev/null
@@ -1,98 +0,0 @@
-/* eslint-disable arrow-parens, class-methods-use-this, no-param-reassign */
-/* global Cookies */
-
-(() => {
- const pinnedStateCookie = 'pin_nav';
- const sidebarBreakpoint = 1024;
-
- const pageSelector = '.page-with-sidebar';
- const navbarSelector = '.navbar-fixed-top';
- const sidebarWrapperSelector = '.sidebar-wrapper';
- const sidebarContentSelector = '.nav-sidebar';
-
- const pinnedToggleSelector = '.js-nav-pin';
- const sidebarToggleSelector = '.toggle-nav-collapse, .side-nav-toggle';
-
- const pinnedPageClass = 'page-sidebar-pinned';
- const expandedPageClass = 'page-sidebar-expanded';
-
- const pinnedNavbarClass = 'header-sidebar-pinned';
- const expandedNavbarClass = 'header-sidebar-expanded';
-
- class Sidebar {
- constructor() {
- if (!Sidebar.singleton) {
- Sidebar.singleton = this;
- Sidebar.singleton.init();
- }
-
- return Sidebar.singleton;
- }
-
- init() {
- this.isPinned = Cookies.get(pinnedStateCookie) === 'true';
- this.isExpanded = (
- window.innerWidth >= sidebarBreakpoint &&
- $(pageSelector).hasClass(expandedPageClass)
- );
- $(document)
- .on('click', sidebarToggleSelector, () => this.toggleSidebar())
- .on('click', pinnedToggleSelector, () => this.togglePinnedState())
- .on('click', 'html, body, a, button', (e) => this.handleClickEvent(e))
- .on('DOMContentLoaded', () => this.renderState())
- .on('todo:toggle', (e, count) => this.updateTodoCount(count));
- this.renderState();
- }
-
- handleClickEvent(e) {
- if (this.isExpanded && (!this.isPinned || window.innerWidth < sidebarBreakpoint)) {
- const $target = $(e.target);
- const targetIsToggle = $target.closest(sidebarToggleSelector).length > 0;
- const targetIsSidebar = $target.closest(sidebarWrapperSelector).length > 0;
- if (!targetIsToggle && (!targetIsSidebar || $target.closest('a'))) {
- this.toggleSidebar();
- }
- }
- }
-
- updateTodoCount(count) {
- $('.js-todos-count').text(gl.text.addDelimiter(count));
- }
-
- toggleSidebar() {
- this.isExpanded = !this.isExpanded;
- this.renderState();
- }
-
- togglePinnedState() {
- this.isPinned = !this.isPinned;
- if (!this.isPinned) {
- this.isExpanded = false;
- }
- Cookies.set(pinnedStateCookie, this.isPinned ? 'true' : 'false', { expires: 3650 });
- this.renderState();
- }
-
- renderState() {
- $(pageSelector)
- .toggleClass(pinnedPageClass, this.isPinned && this.isExpanded)
- .toggleClass(expandedPageClass, this.isExpanded);
- $(navbarSelector)
- .toggleClass(pinnedNavbarClass, this.isPinned && this.isExpanded)
- .toggleClass(expandedNavbarClass, this.isExpanded);
-
- const $pinnedToggle = $(pinnedToggleSelector);
- const tooltipText = this.isPinned ? 'Unpin navigation' : 'Pin navigation';
- const tooltipState = $pinnedToggle.attr('aria-describedby') && this.isExpanded ? 'show' : 'hide';
- $pinnedToggle.attr('title', tooltipText).tooltip('fixTitle').tooltip(tooltipState);
-
- if (this.isExpanded) {
- const sidebarContent = $(sidebarContentSelector);
- setTimeout(() => { sidebarContent.niceScroll().updateScrollBar(); }, 200);
- }
- }
- }
-
- window.gl = window.gl || {};
- gl.Sidebar = Sidebar;
-})();
diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js
index 5b20c63384c..294d087554e 100644
--- a/app/assets/javascripts/single_file_diff.js
+++ b/app/assets/javascripts/single_file_diff.js
@@ -33,13 +33,13 @@
this.$toggleIcon.addClass('fa-caret-down');
}
- $('.file-title, .click-to-expand', this.file).on('click', (function (e) {
+ $('.js-file-title, .click-to-expand', this.file).on('click', (function (e) {
this.toggleDiff($(e.target));
}).bind(this));
}
SingleFileDiff.prototype.toggleDiff = function($target, cb) {
- if (!$target.hasClass('file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return;
+ if (!$target.hasClass('js-file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return;
this.isOpen = !this.isOpen;
if (!this.isOpen && !this.hasError) {
this.content.hide();
@@ -95,4 +95,4 @@
}
});
};
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/snippet/snippet_bundle.js b/app/assets/javascripts/snippet/snippet_bundle.js
index 64f9065be42..89822246bb8 100644
--- a/app/assets/javascripts/snippet/snippet_bundle.js
+++ b/app/assets/javascripts/snippet/snippet_bundle.js
@@ -13,4 +13,4 @@ requireAll(require.context('.', false, /^\.\/(?!snippet_bundle).*\.(js|es6)$/));
$(".snippet-file-content").val(editor.getValue());
});
});
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/star.js b/app/assets/javascripts/star.js
index 531fd0e9c32..c75b44cc2fd 100644
--- a/app/assets/javascripts/star.js
+++ b/app/assets/javascripts/star.js
@@ -27,4 +27,4 @@
return Star;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/subscription_select.js b/app/assets/javascripts/subscription_select.js
index 187356f0bf9..8b25f43ffc7 100644
--- a/app/assets/javascripts/subscription_select.js
+++ b/app/assets/javascripts/subscription_select.js
@@ -31,4 +31,4 @@
return SubscriptionSelect;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/syntax_highlight.js b/app/assets/javascripts/syntax_highlight.js
index 115716bff6a..7c063fae045 100644
--- a/app/assets/javascripts/syntax_highlight.js
+++ b/app/assets/javascripts/syntax_highlight.js
@@ -24,4 +24,4 @@
}
}
};
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/task_list.js b/app/assets/javascripts/task_list.js
new file mode 100644
index 00000000000..dfe24d1fb33
--- /dev/null
+++ b/app/assets/javascripts/task_list.js
@@ -0,0 +1,40 @@
+require('vendor/task_list');
+
+class TaskList {
+ constructor(options = {}) {
+ this.selector = options.selector;
+ this.dataType = options.dataType;
+ this.fieldName = options.fieldName;
+ this.onSuccess = options.onSuccess || (() => {});
+ this.init();
+ }
+
+ init() {
+ // Prevent duplicate event bindings
+ this.disable();
+ $(`${this.selector} .js-task-list-container`).taskList('enable');
+ $(document).on('tasklist:changed', `${this.selector} .js-task-list-container`, this.update.bind(this));
+ }
+
+ disable() {
+ $(`${this.selector} .js-task-list-container`).taskList('disable');
+ $(document).off('tasklist:changed', `${this.selector} .js-task-list-container`);
+ }
+
+ update(e) {
+ const $target = $(e.target);
+ const patchData = {};
+ patchData[this.dataType] = {
+ [this.fieldName]: $target.val(),
+ };
+ return $.ajax({
+ type: 'PATCH',
+ url: $target.data('update-url') || $('form.js-issuable-update').attr('action'),
+ data: patchData,
+ success: this.onSuccess,
+ });
+ }
+}
+
+window.gl = window.gl || {};
+window.gl.TaskList = TaskList;
diff --git a/app/assets/javascripts/boards/test_utils/simulate_drag.js b/app/assets/javascripts/test_utils/simulate_drag.js
index f05780167bf..7dba5840c8a 100644
--- a/app/assets/javascripts/boards/test_utils/simulate_drag.js
+++ b/app/assets/javascripts/test_utils/simulate_drag.js
@@ -50,14 +50,15 @@
return (
children[target.index] ||
children[target.index === 'first' ? 0 : -1] ||
- children[target.index === 'last' ? children.length - 1 : -1]
+ children[target.index === 'last' ? children.length - 1 : -1] ||
+ el
);
}
function getRect(el) {
var rect = el.getBoundingClientRect();
var width = rect.right - rect.left;
- var height = rect.bottom - rect.top;
+ var height = rect.bottom - rect.top + 10;
return {
x: rect.left,
diff --git a/app/assets/javascripts/todos.js.es6 b/app/assets/javascripts/todos.js.es6
index b07e62a8c30..e9513725d9d 100644
--- a/app/assets/javascripts/todos.js.es6
+++ b/app/assets/javascripts/todos.js.es6
@@ -1,28 +1,34 @@
-/* eslint-disable class-methods-use-this, no-new, func-names, prefer-template, no-unneeded-ternary, object-shorthand, space-before-function-paren, comma-dangle, quote-props, consistent-return, no-else-return, no-param-reassign, max-len */
+/* eslint-disable class-methods-use-this, no-new, func-names, no-unneeded-ternary, object-shorthand, quote-props, no-param-reassign, max-len */
/* global UsersSelect */
((global) => {
class Todos {
- constructor({ el } = {}) {
- this.allDoneClicked = this.allDoneClicked.bind(this);
- this.doneClicked = this.doneClicked.bind(this);
- this.el = el || $('.js-todos-options');
- this.perPage = this.el.data('perPage');
- this.clearListeners();
- this.initBtnListeners();
+ constructor() {
this.initFilters();
+ this.bindEvents();
+
+ this.cleanupWrapper = this.cleanup.bind(this);
+ document.addEventListener('beforeunload', this.cleanupWrapper);
}
- clearListeners() {
- $('.done-todo').off('click');
- $('.js-todos-mark-all').off('click');
- return $('.todo').off('click');
+ cleanup() {
+ this.unbindEvents();
+ document.removeEventListener('beforeunload', this.cleanupWrapper);
}
- initBtnListeners() {
- $('.done-todo').on('click', this.doneClicked);
- $('.js-todos-mark-all').on('click', this.allDoneClicked);
- return $('.todo').on('click', this.goToTodoUrl);
+ unbindEvents() {
+ $('.js-done-todo, .js-undo-todo').off('click', this.updateStateClickedWrapper);
+ $('.js-todos-mark-all').off('click', this.allDoneClickedWrapper);
+ $('.todo').off('click', this.goToTodoUrl);
+ }
+
+ bindEvents() {
+ this.updateStateClickedWrapper = this.updateStateClicked.bind(this);
+ this.allDoneClickedWrapper = this.allDoneClicked.bind(this);
+
+ $('.js-done-todo, .js-undo-todo').on('click', this.updateStateClickedWrapper);
+ $('.js-todos-mark-all').on('click', this.allDoneClickedWrapper);
+ $('.todo').on('click', this.goToTodoUrl);
}
initFilters() {
@@ -33,7 +39,7 @@
$('form.filter-form').on('submit', function (event) {
event.preventDefault();
- gl.utils.visitUrl(this.action + '&' + $(this).serialize());
+ gl.utils.visitUrl(`${this.action}&${$(this).serialize()}`);
});
}
@@ -44,130 +50,94 @@
filterable: searchFields ? true : false,
search: { fields: searchFields },
data: $dropdown.data('data'),
- clicked: function() {
+ clicked: function () {
return $dropdown.closest('form.filter-form').submit();
- }
+ },
});
}
- doneClicked(e) {
+ updateStateClicked(e) {
e.preventDefault();
- e.stopImmediatePropagation();
- const $target = $(e.currentTarget);
- $target.disable();
- return $.ajax({
+ const target = e.target;
+ target.setAttribute('disabled', '');
+ target.classList.add('disabled');
+ $.ajax({
type: 'POST',
- url: $target.attr('href'),
+ url: target.getAttribute('href'),
dataType: 'json',
data: {
- '_method': 'delete'
+ '_method': target.getAttribute('data-method'),
},
success: (data) => {
- this.redirectIfNeeded(data.count);
- this.clearDone($target.closest('li'));
- return this.updateBadges(data);
- }
+ this.updateState(target);
+ this.updateBadges(data);
+ },
});
}
allDoneClicked(e) {
e.preventDefault();
- e.stopImmediatePropagation();
const $target = $(e.currentTarget);
$target.disable();
- return $.ajax({
+ $.ajax({
type: 'POST',
url: $target.attr('href'),
dataType: 'json',
data: {
- '_method': 'delete'
+ '_method': 'delete',
},
success: (data) => {
$target.remove();
$('.js-todos-all').html('<div class="nothing-here-block">You\'re all done!</div>');
- return this.updateBadges(data);
- }
+ this.updateBadges(data);
+ },
});
}
- clearDone($row) {
- const $ul = $row.closest('ul');
- $row.remove();
- if (!$ul.find('li').length) {
- return $ul.parents('.panel').remove();
+ updateState(target) {
+ const row = target.closest('li');
+ const restoreBtn = row.querySelector('.js-undo-todo');
+ const doneBtn = row.querySelector('.js-done-todo');
+
+ target.removeAttribute('disabled');
+ target.classList.remove('disabled');
+ target.classList.add('hidden');
+
+ if (target === doneBtn) {
+ row.classList.add('done-reversible');
+ restoreBtn.classList.remove('hidden');
+ } else {
+ row.classList.remove('done-reversible');
+ doneBtn.classList.remove('hidden');
}
}
updateBadges(data) {
$(document).trigger('todo:toggle', data.count);
$('.todos-pending .badge').text(data.count);
- return $('.todos-done .badge').text(data.done_count);
- }
-
- getTotalPages() {
- return this.el.data('totalPages');
- }
-
- getCurrentPage() {
- return this.el.data('currentPage');
- }
-
- getTodosPerPage() {
- return this.el.data('perPage');
- }
-
- redirectIfNeeded(total) {
- const currPages = this.getTotalPages();
- const currPage = this.getCurrentPage();
-
- // Refresh if no remaining Todos
- if (!total) {
- window.location.reload();
- return;
- }
- // Do nothing if no pagination
- if (!currPages) {
- return;
- }
-
- const newPages = Math.ceil(total / this.getTodosPerPage());
- let url = location.href;
-
- if (newPages !== currPages) {
- // Redirect to previous page if there's one available
- if (currPages > 1 && currPage === currPages) {
- const pageParams = {
- page: currPages - 1
- };
- url = gl.utils.mergeUrlParams(pageParams, url);
- }
- return gl.utils.visitUrl(url);
- }
+ $('.todos-done .badge').text(data.done_count);
}
goToTodoUrl(e) {
const todoLink = this.dataset.url;
- let targetLink = e.target.getAttribute('href');
-
- if (e.target.tagName === 'IMG') { // See if clicked target was Avatar
- targetLink = e.target.parentElement.getAttribute('href'); // Parent of Avatar is link
- }
if (!todoLink) {
return;
}
if (gl.utils.isMetaClick(e)) {
+ const windowTarget = '_blank';
+ const selected = e.target;
e.preventDefault();
- // Meta-Click on username leads to different URL than todoLink.
- // Turbolinks can resolve that URL, but window.open requires URL manually.
- if (targetLink !== todoLink) {
- return window.open(targetLink, '_blank');
+
+ if (selected.tagName === 'IMG') {
+ const avatarUrl = selected.parentElement.getAttribute('href');
+ window.open(avatarUrl, windowTarget);
} else {
- return window.open(todoLink, '_blank');
+ window.open(todoLink, windowTarget);
}
} else {
- return gl.utils.visitUrl(todoLink);
+ gl.utils.visitUrl(todoLink);
}
}
}
diff --git a/app/assets/javascripts/tree.js b/app/assets/javascripts/tree.js
index b1b35fdbd6c..76a821c7a17 100644
--- a/app/assets/javascripts/tree.js
+++ b/app/assets/javascripts/tree.js
@@ -65,4 +65,4 @@
return TreeView;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/u2f/error.js b/app/assets/javascripts/u2f/error.js
index 86b459e1866..fd1829efe18 100644
--- a/app/assets/javascripts/u2f/error.js
+++ b/app/assets/javascripts/u2f/error.js
@@ -24,4 +24,4 @@
return U2FError;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/u2f/register.js b/app/assets/javascripts/u2f/register.js
index 69d1ff3a39e..17631f2908d 100644
--- a/app/assets/javascripts/u2f/register.js
+++ b/app/assets/javascripts/u2f/register.js
@@ -95,4 +95,4 @@
return U2FRegister;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/u2f/util.js b/app/assets/javascripts/u2f/util.js
index 34e88220b12..813d363db00 100644
--- a/app/assets/javascripts/u2f/util.js
+++ b/app/assets/javascripts/u2f/util.js
@@ -9,4 +9,4 @@
return U2FUtil;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/users/calendar.js b/app/assets/javascripts/users/calendar.js
index 6e40dfdf3d8..5111b260e1c 100644
--- a/app/assets/javascripts/users/calendar.js
+++ b/app/assets/javascripts/users/calendar.js
@@ -221,4 +221,4 @@
return Calendar;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index d4b24d13299..de33a31b411 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -432,4 +432,4 @@
return UsersSelect;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/javascripts/version_check_image.js.es6 b/app/assets/javascripts/version_check_image.js.es6
index 1fa2b5ac399..d4f716acb72 100644
--- a/app/assets/javascripts/version_check_image.js.es6
+++ b/app/assets/javascripts/version_check_image.js.es6
@@ -1,10 +1,10 @@
-(() => {
- class VersionCheckImage {
- static bindErrorEvent(imageElement) {
- imageElement.off('error').on('error', () => imageElement.hide());
- }
+class VersionCheckImage {
+ static bindErrorEvent(imageElement) {
+ imageElement.off('error').on('error', () => imageElement.hide());
}
+}
- window.gl = window.gl || {};
- gl.VersionCheckImage = VersionCheckImage;
-})();
+window.gl = window.gl || {};
+gl.VersionCheckImage = VersionCheckImage;
+
+module.exports = VersionCheckImage;
diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6
index 8106934e864..54e8f977a47 100644
--- a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6
+++ b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6
@@ -62,6 +62,7 @@
<li v-for='artifact in pipeline.details.artifacts'>
<a
rel="nofollow"
+ download
:href='artifact.path'
>
<i class="fa fa-download" aria-hidden="true"></i>
diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6
index e47dc6935d6..9d66d28cc62 100644
--- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6
+++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6
@@ -5,6 +5,7 @@ window.Vue = require('vue');
require('../vue_shared/components/table_pagination');
require('./store');
require('../vue_shared/components/pipelines_table');
+const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_store');
((gl) => {
gl.VuePipelines = Vue.extend({
@@ -22,21 +23,40 @@ require('../vue_shared/components/pipelines_table');
apiScope: 'all',
pageInfo: {},
pagenum: 1,
- count: { all: 0, running_or_pending: 0 },
+ count: {},
pageRequest: false,
};
},
props: ['scope', 'store', 'svgs'],
created() {
- const pagenum = gl.utils.getParameterByName('p');
+ const pagenum = gl.utils.getParameterByName('page');
const scope = gl.utils.getParameterByName('scope');
if (pagenum) this.pagenum = pagenum;
if (scope) this.apiScope = scope;
+
this.store.fetchDataLoop.call(this, Vue, this.pagenum, this.scope, this.apiScope);
},
+
+ beforeUpdate() {
+ if (this.pipelines.length && this.$children) {
+ CommitPipelinesStoreWithTimeAgo.startTimeAgoLoops.call(this, Vue);
+ }
+ },
+
methods: {
+ /**
+ * Changes the URL according to the pagination component.
+ *
+ * If no scope is provided, 'all' is assumed.
+ *
+ * Pagination component sends "null" when no scope is provided.
+ *
+ * @param {Number} pagenum
+ * @param {String} apiScope = 'all'
+ */
change(pagenum, apiScope) {
- gl.utils.visitUrl(`?scope=${apiScope}&p=${pagenum}`);
+ if (!apiScope) apiScope = 'all';
+ gl.utils.visitUrl(`?scope=${apiScope}&page=${pagenum}`);
},
},
template: `
diff --git a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 b/app/assets/javascripts/vue_pipelines_index/stage.js.es6
index 8cc417a9966..67fdd729e41 100644
--- a/app/assets/javascripts/vue_pipelines_index/stage.js.es6
+++ b/app/assets/javascripts/vue_pipelines_index/stage.js.es6
@@ -23,6 +23,13 @@
required: true,
},
},
+
+ updated() {
+ if (this.builds) {
+ this.stopDropdownClickPropagation();
+ }
+ },
+
methods: {
fetchBuilds(e) {
const areaExpanded = e.currentTarget.attributes['aria-expanded'];
@@ -37,17 +44,19 @@
return flash;
});
},
- keepGraph(e) {
- const { target } = e;
-
- if (target.className.indexOf('js-ci-action-icon') >= 0) return null;
-
- if (
- target.parentElement &&
- (target.parentElement.className.indexOf('js-ci-action-icon') >= 0)
- ) return null;
- return e.stopPropagation();
+ /**
+ * When the user right clicks or cmd/ctrl + click in the job name
+ * the dropdown should not be closed and the link should open in another tab,
+ * so we stop propagation of the click event inside the dropdown.
+ *
+ * Since this component is rendered multiple times per page we need to guarantee we only
+ * target the click event of this component.
+ */
+ stopDropdownClickPropagation() {
+ $(this.$el.querySelectorAll('.js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item')).on('click', (e) => {
+ e.stopPropagation();
+ });
},
},
computed: {
@@ -76,13 +85,13 @@
template: `
<div>
<button
- @click='fetchBuilds($event)'
+ @click="fetchBuilds($event)"
:class="triggerButtonClass"
- :title='stage.title'
+ :title="stage.title"
data-placement="top"
data-toggle="dropdown"
type="button"
- :aria-label='stage.title'
+ :aria-label="stage.title"
>
<span v-html="svg" aria-hidden="true"></span>
<i class="fa fa-caret-down" aria-hidden="true"></i>
@@ -90,7 +99,6 @@
<ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
<div class="arrow-up" aria-hidden="true"></div>
<div
- @click='keepGraph($event)'
:class="dropdownClass"
class="js-builds-dropdown-list scrollable-menu"
v-html="buildsOrSpinner"
diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6
index 0ee21f00fdc..909007267b9 100644
--- a/app/assets/javascripts/vue_pipelines_index/store.js.es6
+++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6
@@ -1,68 +1,31 @@
/* global gl, Flash */
-/* eslint-disable no-param-reassign, no-underscore-dangle */
-require('../vue_realtime_listener');
+/* eslint-disable no-param-reassign */
((gl) => {
const pageValues = (headers) => {
const normalized = gl.utils.normalizeHeaders(headers);
-
- const paginationInfo = {
- perPage: +normalized['X-PER-PAGE'],
- page: +normalized['X-PAGE'],
- total: +normalized['X-TOTAL'],
- totalPages: +normalized['X-TOTAL-PAGES'],
- nextPage: +normalized['X-NEXT-PAGE'],
- previousPage: +normalized['X-PREV-PAGE'],
- };
-
+ const paginationInfo = gl.utils.parseIntPagination(normalized);
return paginationInfo;
};
gl.PipelineStore = class {
fetchDataLoop(Vue, pageNum, url, apiScope) {
this.pageRequest = true;
- const updatePipelineNums = (count) => {
- const { all } = count;
- const running = count.running_or_pending;
- document.querySelector('.js-totalbuilds-count').innerHTML = all;
- document.querySelector('.js-running-count').innerHTML = running;
- };
-
- const goFetch = () =>
- this.$http.get(`${url}?scope=${apiScope}&page=${pageNum}`)
- .then((response) => {
- const pageInfo = pageValues(response.headers);
- this.pageInfo = Object.assign({}, this.pageInfo, pageInfo);
-
- const res = JSON.parse(response.body);
- this.count = Object.assign({}, this.count, res.count);
- this.pipelines = Object.assign([], this.pipelines, res.pipelines);
-
- updatePipelineNums(this.count);
- this.pageRequest = false;
- }, () => {
- this.pageRequest = false;
- return new Flash('An error occurred while fetching the pipelines, please reload the page again.');
- });
-
- goFetch();
-
- const startTimeLoops = () => {
- this.timeLoopInterval = setInterval(() => {
- this.$children[0].$children.reduce((acc, component) => {
- const timeAgoComponent = component.$children.filter(el => el.$options._componentTag === 'time-ago')[0];
- acc.push(timeAgoComponent);
- return acc;
- }, []).forEach(e => e.changeTime());
- }, 10000);
- };
- startTimeLoops();
+ return this.$http.get(`${url}?scope=${apiScope}&page=${pageNum}`)
+ .then((response) => {
+ const pageInfo = pageValues(response.headers);
+ this.pageInfo = Object.assign({}, this.pageInfo, pageInfo);
- const removeIntervals = () => clearInterval(this.timeLoopInterval);
- const startIntervals = () => startTimeLoops();
+ const res = JSON.parse(response.body);
+ this.count = Object.assign({}, this.count, res.count);
+ this.pipelines = Object.assign([], this.pipelines, res.pipelines);
- gl.VueRealtimeListener(removeIntervals, startIntervals);
+ this.pageRequest = false;
+ }, () => {
+ this.pageRequest = false;
+ return new Flash('An error occurred while fetching the pipelines, please reload the page again.');
+ });
}
};
})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/vue_realtime_listener/index.js.es6 b/app/assets/javascripts/vue_realtime_listener/index.js.es6
index 95564152cce..30f6680a673 100644
--- a/app/assets/javascripts/vue_realtime_listener/index.js.es6
+++ b/app/assets/javascripts/vue_realtime_listener/index.js.es6
@@ -14,5 +14,16 @@
window.addEventListener('focus', startIntervals);
window.addEventListener('blur', removeIntervals);
document.addEventListener('beforeunload', removeAll);
+
+ // add removeAll methods to stack
+ const stack = gl.VueRealtimeListener.reset;
+ gl.VueRealtimeListener.reset = () => {
+ gl.VueRealtimeListener.reset = stack;
+ removeAll();
+ stack();
+ };
};
+
+ // remove all event listeners and intervals
+ gl.VueRealtimeListener.reset = () => undefined; // noop
})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/vue_shared/components/commit.js.es6 b/app/assets/javascripts/vue_shared/components/commit.js.es6
index 7f7c18ddeb1..ff88e236829 100644
--- a/app/assets/javascripts/vue_shared/components/commit.js.es6
+++ b/app/assets/javascripts/vue_shared/components/commit.js.es6
@@ -1,4 +1,5 @@
/* global Vue */
+window.Vue = require('vue');
(() => {
window.gl = window.gl || {};
diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6 b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6
index c819f0dd7cd..61c1b72d9d2 100644
--- a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6
+++ b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6
@@ -111,7 +111,7 @@ require('./commit');
* If provided, returns the commit ref.
* Needed to render the commit component column.
*
- * Matched `url` prop sent in the API to `path` prop needed
+ * Matches `path` prop sent in the API to `ref_url` prop needed
* in the commit component.
*
* @returns {Object|Undefined}
@@ -119,8 +119,8 @@ require('./commit');
commitRef() {
if (this.pipeline.ref) {
return Object.keys(this.pipeline.ref).reduce((accumulator, prop) => {
- if (prop === 'url') {
- accumulator.path = this.pipeline.ref[prop];
+ if (prop === 'path') {
+ accumulator.ref_url = this.pipeline.ref[prop];
} else {
accumulator[prop] = this.pipeline.ref[prop];
}
diff --git a/app/assets/javascripts/vue_shared/components/table_pagination.js.es6 b/app/assets/javascripts/vue_shared/components/table_pagination.js.es6
index 67c6cb73761..d8042a9b7fc 100644
--- a/app/assets/javascripts/vue_shared/components/table_pagination.js.es6
+++ b/app/assets/javascripts/vue_shared/components/table_pagination.js.es6
@@ -57,9 +57,7 @@ window.Vue = require('vue');
},
methods: {
changePage(e) {
- let apiScope = gl.utils.getParameterByName('scope');
-
- if (!apiScope) apiScope = 'all';
+ const apiScope = gl.utils.getParameterByName('scope');
const text = e.target.innerText;
const { totalPages, nextPage, previousPage } = this.pageInfo;
diff --git a/app/assets/javascripts/wikis.js.es6 b/app/assets/javascripts/wikis.js.es6
index ef99b2e92f0..75fd1394a03 100644
--- a/app/assets/javascripts/wikis.js.es6
+++ b/app/assets/javascripts/wikis.js.es6
@@ -1,14 +1,10 @@
/* eslint-disable no-param-reassign */
/* global Breakpoints */
-require('vendor/latinise');
require('./breakpoints');
require('vendor/jquery.nicescroll');
((global) => {
- const dasherize = str => str.replace(/[_\s]+/g, '-');
- const slugify = str => dasherize(str.trim().toLowerCase().latinise());
-
class Wikis {
constructor() {
this.bp = Breakpoints.get();
@@ -34,7 +30,7 @@ require('vendor/jquery.nicescroll');
if (!this.newWikiForm) return;
const slugInput = this.newWikiForm.querySelector('#new_wiki_path');
- const slug = slugify(slugInput.value);
+ const slug = gl.text.slugify(slugInput.value);
if (slug.length > 0) {
const wikisPath = slugInput.getAttribute('data-wikis-path');
diff --git a/app/assets/javascripts/zen_mode.js b/app/assets/javascripts/zen_mode.js
index d9261cda1b1..ce626cf7b46 100644
--- a/app/assets/javascripts/zen_mode.js
+++ b/app/assets/javascripts/zen_mode.js
@@ -94,4 +94,4 @@ require('mousetrap/plugins/pause/mousetrap-pause');
return ZenMode;
})();
-}).call(this);
+}).call(window);
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 8b93665d085..1dcd1f8a6fc 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -2,7 +2,6 @@
* This is a manifest file that'll automatically include all the stylesheets available in this directory
* and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
* the top of the compiled file, but it's generally better to create a new file per style scope.
- *= require jquery-ui/datepicker
*= require jquery-ui/autocomplete
*= require jquery.atwho
*= require select2
@@ -19,6 +18,8 @@
* directory.
*/
+@import "../../../node_modules/pikaday/scss/pikaday";
+
/*
* GitLab UI framework
*/
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 08f203a1bf6..39cf3b5f8ae 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -19,7 +19,6 @@
@import "framework/flash.scss";
@import "framework/forms.scss";
@import "framework/gfm.scss";
-@import "framework/gitlab-theme.scss";
@import "framework/header.scss";
@import "framework/highlight.scss";
@import "framework/issue_box.scss";
diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss
index 0a26b4c6a8c..90935b9616b 100644
--- a/app/assets/stylesheets/framework/animations.scss
+++ b/app/assets/stylesheets/framework/animations.scss
@@ -116,7 +116,7 @@
}
.btn,
-.side-nav-toggle {
+.global-dropdown-toggle {
@include transition(background-color, border-color, color, box-shadow);
}
@@ -128,8 +128,7 @@
.note-action-button .link-highlight,
.toolbar-btn,
-.dropdown-toggle-caret,
-.fa:not(.fa-bell) {
+.dropdown-toggle-caret {
@include transition(color);
}
@@ -141,7 +140,6 @@ a {
@include transition(background-color, box-shadow);
}
-.nav-sidebar a,
.dropdown-menu a,
.dropdown-menu button,
.dropdown-menu-nav a {
diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
index 1d59700543c..3f5b78ed445 100644
--- a/app/assets/stylesheets/framework/avatar.scss
+++ b/app/assets/stylesheets/framework/avatar.scss
@@ -28,6 +28,8 @@
.avatar {
@extend .avatar-circle;
+ @include transition-property(none);
+
width: 40px;
height: 40px;
padding: 0;
diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss
index 1d2d1bfc0d7..fb8ea18d122 100644
--- a/app/assets/stylesheets/framework/calendar.scss
+++ b/app/assets/stylesheets/framework/calendar.scss
@@ -9,6 +9,8 @@
}
.user-calendar-activities {
+ direction: ltr;
+
.str-truncated {
max-width: 70%;
}
@@ -43,3 +45,56 @@
float: right;
font-size: 12px;
}
+
+.pika-single.gitlab-theme {
+ .pika-label {
+ color: $gl-text-color-secondary;
+ font-size: 14px;
+ font-weight: normal;
+ }
+
+ th {
+ padding: 2px 0;
+ color: $note-disabled-comment-color;
+ font-weight: normal;
+ text-transform: lowercase;
+ border-top: 1px solid $calendar-border-color;
+ }
+
+ abbr {
+ cursor: default;
+ }
+
+ td {
+ border: 1px solid $calendar-border-color;
+
+ &:first-child {
+ border-left: 0;
+ }
+
+ &:last-child {
+ border-right: 0;
+ }
+ }
+
+ .pika-day {
+ border-radius: 0;
+ background-color: $white-light;
+ text-align: center;
+ }
+
+ .is-today {
+ .pika-day {
+ color: inherit;
+ font-weight: normal;
+ }
+ }
+
+ .is-selected .pika-day,
+ .pika-day:hover,
+ .is-today .pika-day:hover {
+ background: $gl-primary;
+ color: $white-light;
+ box-shadow: none;
+ }
+}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index ca5861bf3e6..ff31e7f7b3d 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -125,7 +125,6 @@
top: 100%;
left: 0;
z-index: 9;
- max-width: 280px;
min-width: 240px;
margin-top: 2px;
margin-bottom: 0;
@@ -137,6 +136,10 @@
border-radius: $border-radius-base;
box-shadow: 0 2px 4px $dropdown-shadow-color;
+ .filtered-search-input-container & {
+ max-width: 280px;
+ }
+
&.is-loading {
.dropdown-content {
display: none;
@@ -502,119 +505,16 @@
max-height: 230px;
}
- .ui-widget {
- table {
- margin: 0;
- }
-
- &.ui-datepicker-inline {
- padding: 0 10px;
- border: 0;
- width: 100%;
- }
-
- .ui-datepicker-header {
- padding: 0 8px 10px;
- border: 0;
-
- .ui-icon {
- background: none;
- font-size: 20px;
- text-indent: 0;
-
- &::before {
- display: block;
- position: relative;
- top: -2px;
- color: $dropdown-title-btn-color;
- font: normal normal normal 14px/1 FontAwesome;
- font-size: inherit;
- text-rendering: auto;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
- }
- }
- }
-
- .ui-datepicker-calendar {
- .ui-state-hover,
- .ui-state-active {
- color: $white-light;
- border: 0;
- }
- }
-
- .ui-datepicker-prev,
- .ui-datepicker-next {
- top: 0;
- height: 15px;
- cursor: pointer;
-
- &:hover {
- background-color: transparent;
- border: 0;
-
- .ui-icon::before {
- color: $md-link-color;
- }
- }
- }
-
- .ui-datepicker-prev {
- left: 0;
-
- .ui-icon::before {
- content: '\f104';
- text-align: left;
- }
- }
-
- .ui-datepicker-next {
- right: 0;
-
- .ui-icon::before {
- content: '\f105';
- text-align: right;
- }
- }
-
- td {
- padding: 0;
- border: 1px solid $calendar-border-color;
-
- &:first-child {
- border-left: 0;
- }
-
- &:last-child {
- border-right: 0;
- }
-
- a {
- line-height: 17px;
- border: 0;
- border-radius: 0;
- }
- }
-
- .ui-datepicker-title {
- color: $gl-text-color;
- font-size: 14px;
- line-height: 1;
- font-weight: normal;
- }
- }
-
- th {
- padding: 2px 0;
- color: $note-disabled-comment-color;
- font-weight: normal;
- text-transform: lowercase;
- border-top: 1px solid $calendar-border-color;
+ .pika-single {
+ position: relative!important;
+ top: 0!important;
+ border: 0;
+ box-shadow: none;
}
- .ui-datepicker-unselectable {
- background-color: $gray-light;
+ .pika-lendar {
+ margin-top: -5px;
+ margin-bottom: 0;
}
}
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index c51912b4ac4..30f242a35db 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -231,3 +231,46 @@ span.idiff {
}
}
}
+
+.file-title-flex-parent {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ background-color: $gray-light;
+ border-bottom: 1px solid $border-color;
+ padding: 5px $gl-padding;
+ margin: 0;
+ border-radius: 3px 3px 0 0;
+
+ .file-header-content {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ padding-right: 30px;
+ position: relative;
+ }
+
+ .btn-clipboard {
+ position: absolute;
+ right: 0;
+ }
+
+ a {
+ color: $gl-text-color;
+ }
+
+ small {
+ margin: 0 10px 0 0;
+ }
+
+ .file-actions {
+ white-space: nowrap;
+
+ .btn {
+ padding: 0 10px;
+ font-size: 13px;
+ line-height: 28px;
+ display: inline-block;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss
deleted file mode 100644
index d6566dc4ec9..00000000000
--- a/app/assets/stylesheets/framework/gitlab-theme.scss
+++ /dev/null
@@ -1,144 +0,0 @@
-/**
- * Styles the GitLab application with a specific color theme
- *
- * $color-light -
- * $color -
- * $color-darker -
- * $color-dark -
- */
-@mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) {
- .page-with-sidebar {
- .toggle-nav-collapse,
- .pin-nav-btn {
- color: $color-light;
-
- &:hover {
- color: $white-light;
- }
- }
-
- .sidebar-wrapper {
- background: $color-darker;
- }
-
- .sidebar-action-buttons {
- color: $color-light;
- background-color: lighten($color-darker, 5%);
- }
-
- .nav-sidebar {
- li {
- a {
- color: $color-light;
-
- &:hover,
- &:focus,
- &:active {
- background: $color-dark;
- }
-
- i {
- color: $color-light;
- }
-
- path,
- polygon {
- fill: $color-light;
- }
-
- .count {
- color: $color-light;
- background: $color-dark;
- }
-
- svg {
- position: relative;
- top: 3px;
- }
- }
-
- &.separate-item {
- border-top: 1px solid $color;
- }
-
- &.active a {
- color: $white-light;
- background: $color-dark;
-
- &.no-highlight {
- border: none;
- }
-
- i {
- color: $white-light;
- }
-
- path,
- polygon {
- fill: $white-light;
- }
- }
- }
-
- .about-gitlab {
- color: $color-light;
- }
- }
- }
-}
-
-$theme-charcoal-light: #b9bbbe;
-$theme-charcoal: #485157;
-$theme-charcoal-dark: #3d454d;
-$theme-charcoal-darker: #383f45;
-
-$theme-blue-light: #becde9;
-$theme-blue: #2980b9;
-$theme-blue-dark: #1970a9;
-$theme-blue-darker: #096099;
-
-$theme-graphite-light: #ccc;
-$theme-graphite: #777;
-$theme-graphite-dark: #666;
-$theme-graphite-darker: #555;
-
-$theme-black-light: #979797;
-$theme-black: #373737;
-$theme-black-dark: #272727;
-$theme-black-darker: #222;
-
-$theme-green-light: #adc;
-$theme-green: #019875;
-$theme-green-dark: #018865;
-$theme-green-darker: #017855;
-
-$theme-violet-light: #98c;
-$theme-violet: #548;
-$theme-violet-dark: #436;
-$theme-violet-darker: #325;
-
-body {
- &.ui_blue {
- @include gitlab-theme($theme-blue-light, $theme-blue, $theme-blue-dark, $theme-blue-darker);
- }
-
- &.ui_charcoal {
- @include gitlab-theme($theme-charcoal-light, $theme-charcoal, $theme-charcoal-dark, $theme-charcoal-darker);
- }
-
- &.ui_graphite {
- @include gitlab-theme($theme-graphite-light, $theme-graphite, $theme-graphite-dark, $theme-graphite-darker);
- }
-
- &.ui_black {
- @include gitlab-theme($theme-black-light, $theme-black, $theme-black-dark, $theme-black-darker);
- }
-
- &.ui_green {
- @include gitlab-theme($theme-green-light, $theme-green, $theme-green-dark, $theme-green-darker);
- }
-
- &.ui_violet {
- @include gitlab-theme($theme-violet-light, $theme-violet, $theme-violet-dark, $theme-violet-darker);
- }
-}
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 2a01bc4d44d..3945a789c82 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -100,23 +100,40 @@ header {
}
}
}
+ }
- .side-nav-toggle {
- position: absolute;
- left: -10px;
- margin: 7px 0;
- font-size: 18px;
- padding: 6px 10px;
- border: none;
- background-color: $gray-light;
+ .global-dropdown {
+ position: absolute;
+ left: -10px;
- &:hover {
- background-color: $white-normal;
- color: $gl-header-nav-hover-color;
+ .badge {
+ font-size: 11px;
+ }
+
+ li {
+ &.active a {
+ font-weight: bold;
}
}
}
+ .global-dropdown-toggle {
+ margin: 7px 0;
+ font-size: 18px;
+ padding: 6px 10px;
+ border: none;
+ background-color: $gray-light;
+
+ &:hover {
+ background-color: $white-normal;
+ }
+
+ &:focus {
+ outline: none;
+ background-color: $white-normal;
+ }
+ }
+
.header-content {
position: relative;
height: $header-height;
@@ -222,6 +239,10 @@ header {
float: right;
border-top: none;
+ @media (min-width: $screen-md-min) {
+ padding: 0;
+ }
+
@media (max-width: $screen-xs-max) {
float: none;
}
@@ -272,7 +293,7 @@ header {
.header-user {
.dropdown-menu-nav {
- width: 140px;
+ min-width: 140px;
margin-top: -5px;
}
}
diff --git a/app/assets/stylesheets/framework/jquery.scss b/app/assets/stylesheets/framework/jquery.scss
index 18f2f316f02..d335fedefe2 100644
--- a/app/assets/stylesheets/framework/jquery.scss
+++ b/app/assets/stylesheets/framework/jquery.scss
@@ -2,42 +2,6 @@
font-family: $regular_font;
font-size: $font-size-base;
- &.ui-datepicker,
- &.ui-datepicker-inline {
- border: 1px solid $jq-ui-border;
- padding: 10px;
- width: 270px;
-
- .ui-datepicker-header {
- background: $white-light;
- border-color: $jq-ui-border;
-
- .ui-datepicker-prev,
- .ui-datepicker-next {
- top: 4px;
- }
-
- .ui-datepicker-prev {
- left: 2px;
- }
-
- .ui-datepicker-next {
- right: 2px;
- }
-
- .ui-state-hover {
- background: transparent;
- border: 0;
- cursor: pointer;
- }
- }
-
- .ui-datepicker-calendar td a {
- padding: 5px;
- text-align: center;
- }
- }
-
&.ui-autocomplete {
border-color: $jq-ui-border;
padding: 0;
@@ -59,25 +23,4 @@
border: 0;
background: transparent;
}
-
- .ui-datepicker-calendar {
- .ui-state-active,
- .ui-state-hover,
- .ui-state-focus {
- border: 1px solid $gl-primary;
- background: $gl-primary;
- color: $white-light;
- }
- }
-}
-
-.ui-sortable-handle {
- cursor: move;
- cursor: -webkit-grab;
- cursor: -moz-grab;
-
- &:active {
- cursor: -webkit-grabbing;
- cursor: -moz-grabbing;
- }
}
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index 2bfdb9f9601..55ed4b7b06c 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -96,16 +96,6 @@ ul.unstyled-list > li {
border-bottom: none;
}
-ul.task-list {
- li.task-list-item {
- list-style-type: none;
- }
-
- ul:not(.task-list) {
- padding-left: 1.3em;
- }
-}
-
// Generic content list
ul.content-list {
@include basic-list;
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 1acd06122a3..df78bbdea51 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -76,6 +76,13 @@
#{$property}: $value;
}
+/* http://phrappe.com/css/conditional-css-for-webkit-based-browsers/ */
+@mixin on-webkit-only {
+ @media screen and (-webkit-min-device-pixel-ratio:0) {
+ @content;
+ }
+}
+
@mixin keyframes($animation-name) {
@-webkit-keyframes #{$animation-name} {
@content;
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index fd081c2d7e1..674d3bb45aa 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -283,10 +283,7 @@
}
.layout-nav {
- position: fixed;
- top: $header-height;
width: 100%;
- z-index: 11;
background: $gray-light;
border-bottom: 1px solid $border-color;
transition: padding $sidebar-transition-duration;
@@ -419,15 +416,20 @@
}
.page-with-layout-nav {
- margin-top: $header-height + 2;
-
.right-sidebar {
top: ($header-height * 2) + 2;
}
+
+ .build-sidebar {
+ top: ($header-height * 3) + 3;
+
+ &.affix {
+ top: 0;
+ }
+ }
}
.activities {
-
.nav-block {
border-bottom: 1px solid $border-color;
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index f0b03710c79..8978e284f55 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -1,36 +1,3 @@
-.page-with-sidebar {
- padding: $header-height 0 25px;
- transition: padding $sidebar-transition-duration;
-
- &.page-sidebar-pinned {
- .sidebar-wrapper {
- box-shadow: none;
- }
- }
-
- .sidebar-wrapper {
- position: fixed;
- top: 0;
- bottom: 0;
- left: 0;
- height: 100%;
- width: 0;
- overflow: hidden;
- transition: width $sidebar-transition-duration;
- box-shadow: 2px 0 16px 0 $black-transparent;
- }
-}
-
-.sidebar-wrapper {
- z-index: 1000;
- background: $gray-light;
-
- .nicescroll-rails-hr {
- // TODO: Figure out why nicescroll doesn't hide horizontal bar
- display: none!important;
- }
-}
-
.content-wrapper {
width: 100%;
transition: padding $sidebar-transition-duration;
@@ -47,105 +14,6 @@
}
}
-.nav-sidebar {
- position: absolute;
- top: 50px;
- bottom: 0;
- width: $sidebar_width;
- overflow-y: auto;
- overflow-x: hidden;
-
- &.navbar-collapse {
- padding: 0 !important;
- }
-
- li {
- &.separate-item {
- padding-top: 10px;
- margin-top: 10px;
- }
-
- .icon-container {
- width: 34px;
- display: inline-block;
- text-align: center;
- }
-
- a {
- padding: 7px $gl-sidebar-padding;
- font-size: $gl-font-size;
- line-height: 24px;
- display: block;
- text-decoration: none;
- font-weight: normal;
-
- &:hover,
- &:active,
- &:focus {
- text-decoration: none;
- }
-
- i {
- font-size: 16px;
- }
-
- i,
- svg {
- margin-right: 13px;
- }
- }
- }
-
- .count {
- float: right;
- padding: 0 8px;
- border-radius: 6px;
- }
-
- .about-gitlab {
- padding: 7px $gl-sidebar-padding;
- font-size: $gl-font-size;
- line-height: 24px;
- display: block;
- text-decoration: none;
- font-weight: normal;
- position: absolute;
- bottom: 10px;
- }
-}
-
-.sidebar-action-buttons {
- width: $sidebar_width;
- position: absolute;
- top: 0;
- left: 0;
- min-height: 50px;
- padding: 5px 0;
- font-size: 18px;
- line-height: 30px;
-
- .toggle-nav-collapse {
- left: 0;
- }
-
- .pin-nav-btn {
- right: 0;
- display: none;
-
- @media (min-width: $sidebar-breakpoint) {
- display: block;
- }
-
- .fa {
- transition: transform .15s;
-
- .page-sidebar-pinned & {
- transform: rotate(90deg);
- }
- }
- }
-}
-
.nav-header-btn {
padding: 10px $gl-sidebar-padding;
color: inherit;
@@ -161,57 +29,16 @@
}
}
-.page-sidebar-expanded {
- .sidebar-wrapper {
- width: $sidebar_width;
- }
-}
-
-.page-sidebar-pinned {
- .content-wrapper,
- .layout-nav {
- @media (min-width: $sidebar-breakpoint) {
- padding-left: $sidebar_width;
- }
- }
-
- .merge-request-tabs-holder.affix {
- @media (min-width: $sidebar-breakpoint) {
- left: $sidebar_width;
- }
- }
-
- &.right-sidebar-expanded {
- .line-resolve-all-container {
- @media (min-width: $sidebar-breakpoint) {
- display: none;
- }
- }
- }
-}
-
-header.header-sidebar-pinned {
- @media (min-width: $sidebar-breakpoint) {
- padding-left: ($sidebar_width + $gl-padding);
-
- .side-nav-toggle {
- display: none;
- }
-
- .header-content {
- padding-left: 0;
- }
- }
-}
-
.right-sidebar-collapsed {
padding-right: 0;
@media (min-width: $screen-sm-min) {
- padding-right: $sidebar_collapsed_width;
+ .content-wrapper {
+ padding-right: $gutter_collapsed_width;
+ }
.merge-request-tabs-holder.affix {
- right: $sidebar_collapsed_width;
+ right: $gutter_collapsed_width;
}
}
@@ -228,28 +55,35 @@ header.header-sidebar-pinned {
padding-right: 0;
@media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
- &:not(.build-sidebar):not(.wiki-sidebar) {
- padding-right: $sidebar_collapsed_width;
+ .content-wrapper {
+ padding-right: $gutter_collapsed_width;
}
}
@media (min-width: $screen-md-min) {
- padding-right: $gutter_width;
+ .content-wrapper {
+ padding-right: $gutter_width;
+ }
&:not(.with-overlay) .merge-request-tabs-holder.affix {
right: $gutter_width;
}
&.with-overlay .merge-request-tabs-holder.affix {
- right: $sidebar_collapsed_width;
+ right: $gutter_collapsed_width;
}
}
&.with-overlay {
- padding-right: $sidebar_collapsed_width;
+ padding-right: $gutter_collapsed_width;
}
}
.right-sidebar {
border-left: 1px solid $border-color;
+
+ &.affix {
+ position: fixed;
+ top: 0;
+ }
}
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 54958973f15..db5e2c51fe7 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -134,7 +134,7 @@
ul,
ol {
padding: 0;
- margin: 3px 0 3px 28px !important;
+ margin: 3px 0 !important;
}
ul:dir(rtl),
@@ -144,6 +144,29 @@
li {
line-height: 1.6em;
+ margin-left: 25px;
+ padding-left: 3px;
+
+ /* Normalize the bullet position on webkit. */
+ @include on-webkit-only {
+ margin-left: 28px;
+ padding-left: 0;
+ }
+ }
+
+ ul.task-list {
+ li.task-list-item {
+ list-style-type: none;
+ position: relative;
+ padding-left: 28px;
+ margin-left: 0 !important;
+
+ input.task-list-item-checkbox {
+ position: absolute;
+ left: 8px;
+ top: 5px;
+ }
+ }
}
a[href*="/uploads/"],
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 7809d4866f1..ba0af072716 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -1,8 +1,6 @@
/*
* Layout
*/
-$sidebar_collapsed_width: 62px;
-$sidebar_width: 220px;
$gutter_collapsed_width: 62px;
$gutter_width: 290px;
$gutter_inner_width: 250px;
@@ -541,4 +539,4 @@ Pipeline Graph
*/
$stage-hover-bg: #eaf3fc;
$stage-hover-border: #d1e7fc;
-$action-icon-color: #d6d6d6; \ No newline at end of file
+$action-icon-color: #d6d6d6;
diff --git a/app/assets/stylesheets/mailers/highlighted_diff_email.scss b/app/assets/stylesheets/mailers/highlighted_diff_email.scss
index 60ff72c703e..ea40f449134 100644
--- a/app/assets/stylesheets/mailers/highlighted_diff_email.scss
+++ b/app/assets/stylesheets/mailers/highlighted_diff_email.scss
@@ -138,6 +138,13 @@ pre {
margin: 0;
}
+blockquote {
+ color: $gl-grayish-blue;
+ padding: 0 0 0 15px;
+ margin: 0;
+ border-left: 3px solid $white-dark;
+}
+
span.highlight_word {
background-color: $highlighted-highlight-word !important;
}
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index b362cc758cc..9a36d76136b 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -298,12 +298,8 @@
.issue-boards-sidebar {
&.right-sidebar {
- top: 153px;
+ top: 0;
bottom: 0;
-
- @media (min-width: $screen-sm-min) {
- top: 220px;
- }
}
.issuable-sidebar-header {
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index fd101d43b5b..a24292a7c8c 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -91,7 +91,7 @@
}
&.scroll-top {
- top: 110px;
+ top: 10px;
}
&.scroll-bottom {
diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss
index cda069e6c0e..5b777953fb0 100644
--- a/app/assets/stylesheets/pages/cycle_analytics.scss
+++ b/app/assets/stylesheets/pages/cycle_analytics.scss
@@ -284,7 +284,11 @@
.events-description {
line-height: 65px;
- padding-left: $gl-padding;
+ padding: 0 $gl-padding;
+ }
+
+ .events-info {
+ color: $gl-text-color-secondary;
}
}
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 96ba7c40634..92d7772da57 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -34,9 +34,14 @@
}
}
- .file-title {
+ .file-title,
+ .file-title-flex-parent {
cursor: pointer;
+ a:hover {
+ text-decoration: none;
+ }
+
&:hover {
background-color: $gray-normal;
}
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 778ef01430e..f789ae1ccd3 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -10,6 +10,11 @@
font-size: 34px;
}
+.environments-folder-name {
+ font-weight: normal;
+ padding-top: 20px;
+}
+
@media (max-width: $screen-xs-max) {
.environments-container {
width: 100%;
@@ -30,7 +35,6 @@
display: table-cell;
}
- .environments-name,
.environments-commit,
.environments-actions {
width: 20%;
@@ -40,6 +44,7 @@
width: 10%;
}
+ .environments-name,
.environments-deploy,
.environments-build {
width: 15%;
@@ -57,6 +62,22 @@
}
}
+ .btn-group {
+
+ > a {
+ color: $gl-text-color-secondary;
+ }
+
+ svg path {
+ fill: $gl-text-color-secondary;
+ }
+
+ .dropdown {
+ outline: none;
+ }
+ }
+
+
.commit-title {
margin: 0;
}
@@ -110,17 +131,20 @@
}
}
- .children-row .environment-name {
- margin-left: 17px;
- margin-right: -17px;
- }
-
.folder-icon {
- padding: 0 5px 0 0;
+ margin-right: 3px;
+ color: $gl-text-color-secondary;
+ display: inline-block;
+
+ .fa:nth-child(1) {
+ margin-right: 3px;
+ }
}
.folder-name {
cursor: pointer;
+ color: $gl-text-color-secondary;
+ display: inline-block;
}
}
@@ -135,4 +159,4 @@
margin-right: 0;
}
}
-} \ No newline at end of file
+}
diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss
index b989d72ce1c..5776d86983a 100644
--- a/app/assets/stylesheets/pages/events.scss
+++ b/app/assets/stylesheets/pages/events.scss
@@ -41,7 +41,6 @@
word-wrap: break-word;
.md {
- color: $gl-grayish-blue;
font-size: $gl-font-size;
.label {
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 9174976c4c6..4426169ef5a 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -189,11 +189,10 @@
}
.right-sidebar {
- position: fixed;
+ position: absolute;
top: $header-height;
bottom: 0;
right: 0;
- z-index: 8;
transition: width .3s;
background: $gray-light;
padding: 10px 20px;
@@ -254,11 +253,11 @@
display: block;
}
- width: $sidebar_collapsed_width;
+ width: $gutter_collapsed_width;
padding-top: 0;
.block {
- width: $sidebar_collapsed_width - 2px;
+ width: $gutter_collapsed_width - 2px;
margin-left: -19px;
padding: 15px 0 0;
border-bottom: none;
@@ -461,8 +460,19 @@
.issuable-list {
li {
+
+ .issue-box {
+ display: -webkit-flex;
+ display: flex;
+ }
+
+ .issue-info-container {
+ -webkit-flex: 1;
+ flex: 1;
+ padding-right: $gl-padding;
+ }
+
.issue-check {
- float: left;
padding-right: $gl-padding;
margin-bottom: 10px;
min-width: 15px;
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 1e605337f09..b595480561b 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -1,6 +1,6 @@
.issues-list {
.issue {
- padding: 10px $gl-padding;
+ padding: 10px 0 10px $gl-padding;
position: relative;
.title {
@@ -10,6 +10,11 @@
.issue-labels {
display: inline-block;
}
+
+ .icon-merge-request-unmerged {
+ height: 13px;
+ margin-bottom: 3px;
+ }
}
}
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index 762b95a657c..e1ef0b029a5 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -116,6 +116,22 @@
}
.manage-labels-list {
+ > li:not(.empty-message) {
+ background-color: $white-light;
+ cursor: move;
+ cursor: -webkit-grab;
+ cursor: -moz-grab;
+
+ &:active {
+ cursor: -webkit-grabbing;
+ cursor: -moz-grabbing;
+ }
+
+ &.sortable-ghost {
+ opacity: 0.3;
+ }
+ }
+
.btn-action {
color: $gl-text-color;
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index b01d8d695d6..0b0c4bc130d 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -85,24 +85,21 @@
-webkit-align-items: center;
align-items: center;
+ i,
+ svg {
+ margin-right: 8px;
+ }
+
svg {
- margin-right: 4px;
position: relative;
top: 1px;
overflow: visible;
}
- &> span {
+ & > span {
padding-right: 4px;
}
- &.ci-success_with_warnings {
-
- i {
- color: $gl-warning;
- }
- }
-
@media (max-width: $screen-xs-max) {
flex-wrap: wrap;
}
@@ -125,6 +122,12 @@
line-height: 16px;
}
+ @media (min-width: $screen-sm-min) {
+ .stage-cell {
+ padding: 0 4px;
+ }
+ }
+
@media (max-width: $screen-xs-max) {
order: 1;
margin-top: $gl-padding-top;
@@ -266,8 +269,15 @@
.mr-list {
.merge-request {
- padding: 10px 15px;
+ padding: 10px 0 10px 15px;
position: relative;
+ display: -webkit-flex;
+ display: flex;
+
+ .issue-info-container {
+ -webkit-flex: 1;
+ flex: 1;
+ }
.merge-request-title {
margin-bottom: 2px;
@@ -473,7 +483,7 @@
background-color: $white-light;
&.affix {
- top: 100px;
+ top: 0;
left: 0;
z-index: 10;
transition: right .15s;
diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss
index 686b64cdd24..27c47d36818 100644
--- a/app/assets/stylesheets/pages/milestone.scss
+++ b/app/assets/stylesheets/pages/milestone.scss
@@ -30,6 +30,26 @@
word-wrap: break-word;
}
}
+
+ .panel-heading {
+ line-height: $line-height-base;
+ padding: 14px 16px;
+ display: -webkit-flex;
+ display: flex;
+
+ .title {
+ -webkit-flex: 1;
+ -webkit-flex-grow: 1;
+ flex: 1;
+ flex-grow: 2;
+ }
+
+ .counter {
+ -webkit-flex: 1;
+ flex: 0;
+ padding-left: 16px;
+ }
+ }
}
.milestone-summary {
@@ -178,3 +198,9 @@
}
}
}
+
+.issuable-row {
+ background-color: $white-light;
+ cursor: -webkit-grab;
+ cursor: grab;
+}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index f310cc72da0..aa130a1abb0 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -72,6 +72,7 @@ ul.notes {
overflow: hidden;
.system-note-commit-list-toggler {
+ color: $gl-link-color;
display: none;
padding: 10px 0 0;
cursor: pointer;
@@ -107,16 +108,6 @@ ul.notes {
display: none;
}
- p:last-child {
- a {
- color: $gl-text-color;
-
- &:hover {
- color: $gl-link-color;
- }
- }
- }
-
&::after {
content: '';
width: 100%;
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 974100bdff0..3fe1eef307e 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -94,6 +94,10 @@
padding: 10px 8px;
}
+ td.stage-cell {
+ padding: 10px 0;
+ }
+
.commit-link {
padding: 9px 8px 10px;
}
@@ -218,6 +222,11 @@
}
}
+ .dropdown-menu {
+ max-height: 250px;
+ overflow-y: auto;
+ }
+
.dropdown-toggle,
.dropdown-menu {
color: $gl-text-color-secondary;
@@ -291,12 +300,14 @@
height: 22px;
margin: 3px 6px 3px 0;
- .tooltip {
- white-space: nowrap;
+ // Hack to show a button tooltip inline
+ button.has-tooltip + .tooltip {
+ min-width: 105px;
}
- .tooltip-inner {
- padding: 3px 4px;
+ // Bootstrap way of showing the content inline for anchors.
+ a.has-tooltip {
+ white-space: nowrap;
}
&:not(:last-child) {
@@ -858,7 +869,7 @@
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
- width: 90px;
+ max-width: 70%;
color: $gl-text-color-secondary;
margin-left: 2px;
display: inline-block;
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 722b3006f7c..8031c4467a4 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -201,10 +201,6 @@
color: $note-disabled-comment-color;
}
-.datepicker.personal-access-tokens-expires-at .ui-state-disabled span {
- text-align: center;
-}
-
.created-personal-access-token-container {
#created-personal-access-token {
width: 90%;
diff --git a/app/assets/stylesheets/pages/profiles/preferences.scss b/app/assets/stylesheets/pages/profiles/preferences.scss
index 100ace41f2a..305feaacaa1 100644
--- a/app/assets/stylesheets/pages/profiles/preferences.scss
+++ b/app/assets/stylesheets/pages/profiles/preferences.scss
@@ -1,42 +1,3 @@
-.application-theme {
- label {
- margin-right: 20px;
- text-align: center;
-
- .preview {
- border-radius: 4px;
-
- height: 80px;
- margin-bottom: 10px;
- width: 160px;
-
- &.ui_blue {
- background: $theme-blue;
- }
-
- &.ui_charcoal {
- background: $theme-charcoal;
- }
-
- &.ui_graphite {
- background: $theme-graphite;
- }
-
- &.ui_black {
- background: $theme-black;
- }
-
- &.ui_green {
- background: $theme-green;
- }
-
- &.ui_violet {
- background: $theme-violet;
- }
- }
- }
-}
-
.syntax-theme {
label {
margin-right: 20px;
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 8b59c20cb65..67110813abb 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -35,12 +35,8 @@
margin-bottom: 10px;
}
- .project-path {
- padding-right: 0;
-
- .form-control {
- border-radius: $border-radius-base;
- }
+ .project-path .form-control {
+ border-radius: $border-radius-base;
}
.input-group > div {
@@ -106,6 +102,7 @@
font-size: 24px;
font-weight: 400;
line-height: 1;
+ word-wrap: break-word;
.fa {
margin-left: 2px;
@@ -271,6 +268,13 @@
}
}
+.project-repo-buttons {
+ .project-action-button .dropdown-menu {
+ max-height: 250px;
+ overflow-y: auto;
+ }
+}
+
.split-one {
display: inline-table;
margin-right: 12px;
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index 0d5604aae69..af9ddb9ff80 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -6,6 +6,8 @@
.navbar-nav {
li {
.badge.todos-pending-count {
+ position: inherit;
+ top: -6px;
margin-top: -5px;
font-weight: normal;
background: $todo-alert-blue;
@@ -43,6 +45,12 @@
}
}
+ .todo-avatar,
+ .todo-actions {
+ -webkit-flex: 0 0 auto;
+ flex: 0 0 auto;
+ }
+
.todo-actions {
display: -webkit-flex;
display: flex;
@@ -55,15 +63,49 @@
}
.todo-item {
- -webkit-flex: auto;
- flex: auto;
+ -webkit-flex: 0 1 100%;
+ flex: 0 1 100%;
+ min-width: 0;
+ }
+}
+
+.todos-list > .todo.todo-pending.done-reversible {
+ background-color: $gray-light;
+
+ &:hover {
+ border-color: $border-color;
+ }
+
+ .title {
+ font-weight: normal;
}
}
.todo-item {
.todo-title {
- @include str-truncated(calc(100% - 174px));
- overflow: visible;
+ display: flex;
+
+ & > .title-item {
+ -webkit-flex: 0 0 auto;
+ flex: 0 0 auto;
+ margin: 0 2px;
+
+ &:first-child {
+ margin-left: 0;
+ }
+
+ &:last-child {
+ margin-right: 0;
+ }
+ }
+
+ .todo-label {
+ -webkit-flex: 0 1 auto;
+ flex: 0 1 auto;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
}
.status-box {
@@ -142,10 +184,12 @@
.todo-item {
.todo-title {
- white-space: normal;
- overflow: visible;
- max-width: 100%;
+ flex-flow: row wrap;
margin-bottom: 10px;
+
+ .todo-label {
+ white-space: normal;
+ }
}
.todo-body {
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 948921efc0b..e4487dbcb87 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -149,7 +149,7 @@
}
.commit-actions {
- width: 200px;
+ width: 260px;
}
}
diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss
index d5783e14b21..9bc47bbe173 100644
--- a/app/assets/stylesheets/pages/wiki.scss
+++ b/app/assets/stylesheets/pages/wiki.scss
@@ -1,3 +1,11 @@
+.new-wiki-page {
+ .new-wiki-page-slug-tip {
+ display: inline-block;
+ max-width: 100%;
+ margin-top: 5px;
+ }
+}
+
.title .edit-wiki-header {
width: 780px;
margin-left: auto;
@@ -9,12 +17,18 @@
@extend .top-area;
position: relative;
+ .wiki-breadcrumb {
+ border-bottom: 1px solid $white-normal;
+ padding: 11px 0;
+ }
+
.wiki-page-title {
margin: 0;
font-size: 22px;
}
.wiki-last-edit-by {
+ display: block;
color: $gl-text-color-secondary;
strong {
@@ -121,6 +135,10 @@
margin: 5px 0 10px;
}
+ ul.wiki-pages ul {
+ padding-left: 15px;
+ }
+
.wiki-sidebar-header {
padding: 0 $gl-padding $gl-padding;
@@ -129,3 +147,15 @@
}
}
}
+
+ul.wiki-pages-list.content-list {
+ & ul {
+ list-style: none;
+ margin-left: 0;
+ padding-left: 15px;
+ }
+
+ & ul li {
+ padding: 5px 0;
+ }
+}
diff --git a/app/assets/stylesheets/print.scss b/app/assets/stylesheets/print.scss
index 0ff3c3f5472..6cc1cc8e263 100644
--- a/app/assets/stylesheets/print.scss
+++ b/app/assets/stylesheets/print.scss
@@ -31,7 +31,6 @@ nav.navbar-collapse.collapse,
.blob-commit-info,
.file-title,
.file-holder,
-.sidebar-wrapper,
.nav,
.btn,
ul.notes-form,
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index b0f5d4a9933..d807e6263ee 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -83,6 +83,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:akismet_api_key,
:akismet_enabled,
:container_registry_token_expire_delay,
+ :default_artifacts_expire_in,
:default_branch_protection,
:default_group_visibility,
:default_project_visibility,
diff --git a/app/controllers/admin/background_jobs_controller.rb b/app/controllers/admin/background_jobs_controller.rb
index 338496013a0..c09095b9849 100644
--- a/app/controllers/admin/background_jobs_controller.rb
+++ b/app/controllers/admin/background_jobs_controller.rb
@@ -2,5 +2,6 @@ class Admin::BackgroundJobsController < Admin::ApplicationController
def show
ps_output, _ = Gitlab::Popen.popen(%W(ps -U #{Gitlab.config.gitlab.user} -o pid,pcpu,pmem,stat,start,command))
@sidekiq_processes = ps_output.split("\n").grep(/sidekiq/)
+ @concurrency = Sidekiq.options[:concurrency]
end
end
diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb
index c491e5c7550..8360ce08bdc 100644
--- a/app/controllers/admin/dashboard_controller.rb
+++ b/app/controllers/admin/dashboard_controller.rb
@@ -1,7 +1,7 @@
class Admin::DashboardController < Admin::ApplicationController
def index
- @projects = Project.limit(10)
+ @projects = Project.with_route.limit(10)
@users = User.limit(10)
- @groups = Group.limit(10)
+ @groups = Group.with_route.limit(10)
end
end
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index b7722a1d15d..cea3d088e94 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -2,7 +2,7 @@ class Admin::GroupsController < Admin::ApplicationController
before_action :group, only: [:edit, :update, :destroy, :project_update, :members_update]
def index
- @groups = Group.with_statistics
+ @groups = Group.with_statistics.with_route
@groups = @groups.sort(@sort = params[:sort])
@groups = @groups.search(params[:name]) if params[:name].present?
@groups = @groups.page(params[:page])
@@ -49,7 +49,7 @@ class Admin::GroupsController < Admin::ApplicationController
end
def destroy
- DestroyGroupService.new(@group, current_user).async_execute
+ Groups::DestroyService.new(@group, current_user).async_execute
redirect_to admin_groups_path, alert: "Group '#{@group.name}' was scheduled for deletion."
end
diff --git a/app/controllers/admin/runners_controller.rb b/app/controllers/admin/runners_controller.rb
index 7345c91f67d..348641e5ecb 100644
--- a/app/controllers/admin/runners_controller.rb
+++ b/app/controllers/admin/runners_controller.rb
@@ -13,7 +13,7 @@ class Admin::RunnersController < Admin::ApplicationController
end
def update
- if @runner.update_attributes(runner_params)
+ if Ci::UpdateRunnerService.new(@runner).update(runner_params)
respond_to do |format|
format.js
format.html { redirect_to admin_runner_path(@runner) }
@@ -31,7 +31,7 @@ class Admin::RunnersController < Admin::ApplicationController
end
def resume
- if @runner.update_attributes(active: true)
+ if Ci::UpdateRunnerService.new(@runner).update(active: true)
redirect_to admin_runners_path, notice: 'Runner was successfully updated.'
else
redirect_to admin_runners_path, alert: 'Runner was not updated.'
@@ -39,7 +39,7 @@ class Admin::RunnersController < Admin::ApplicationController
end
def pause
- if @runner.update_attributes(active: false)
+ if Ci::UpdateRunnerService.new(@runner).update(active: false)
redirect_to admin_runners_path, notice: 'Runner was successfully updated.'
else
redirect_to admin_runners_path, alert: 'Runner was not updated.'
diff --git a/app/controllers/admin/system_info_controller.rb b/app/controllers/admin/system_info_controller.rb
index ca04a17caa1..99039724521 100644
--- a/app/controllers/admin/system_info_controller.rb
+++ b/app/controllers/admin/system_info_controller.rb
@@ -3,7 +3,7 @@ class Admin::SystemInfoController < Admin::ApplicationController
'nobrowse',
'read-only',
'ro'
- ]
+ ].freeze
EXCLUDED_MOUNT_TYPES = [
'autofs',
@@ -21,12 +21,13 @@ class Admin::SystemInfoController < Admin::ApplicationController
'mqueue',
'proc',
'pstore',
+ 'rpc_pipefs',
'securityfs',
'sysfs',
'tmpfs',
'tracefs',
'vfat'
- ]
+ ].freeze
def show
@cpus = Vmstat.cpu rescue nil
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index 1cd50852e89..7ffde71c3b1 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -194,7 +194,6 @@ class Admin::UsersController < Admin::ApplicationController
:provider,
:remember_me,
:skype,
- :theme_id,
:twitter,
:username,
:website_url
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index bb47e2a8bf7..e42e48f87d2 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -12,7 +12,6 @@ class ApplicationController < ActionController::Base
before_action :authenticate_user_from_private_token!
before_action :authenticate_user!
before_action :validate_user_service_ticket!
- before_action :reject_blocked!
before_action :check_password_expiration
before_action :check_2fa_requirement
before_action :ldap_security_check
@@ -75,7 +74,7 @@ class ApplicationController < ActionController::Base
def authenticate_user!(*args)
if redirect_to_home_page_url?
- redirect_to current_application_settings.home_page_url and return
+ return redirect_to current_application_settings.home_page_url
end
super(*args)
@@ -87,22 +86,8 @@ class ApplicationController < ActionController::Base
logger.error "\n#{exception.class.name} (#{exception.message}):\n#{application_trace.join}"
end
- def reject_blocked!
- if current_user && current_user.blocked?
- sign_out current_user
- flash[:alert] = "Your account is blocked. Retry when an admin has unblocked it."
- redirect_to new_user_session_path
- end
- end
-
def after_sign_in_path_for(resource)
- if resource.is_a?(User) && resource.respond_to?(:blocked?) && resource.blocked?
- sign_out resource
- flash[:alert] = "Your account is blocked. Retry when an admin has unblocked it."
- new_user_session_path
- else
- stored_location_for(:redirect) || stored_location_for(resource) || root_path
- end
+ stored_location_for(:redirect) || stored_location_for(resource) || root_path
end
def after_sign_out_path_for(resource)
@@ -146,7 +131,7 @@ class ApplicationController < ActionController::Base
headers['X-UA-Compatible'] = 'IE=edge'
headers['X-Content-Type-Options'] = 'nosniff'
# Enabling HSTS for non-standard ports would send clients to the wrong port
- if Gitlab.config.gitlab.https and Gitlab.config.gitlab.port == 443
+ if Gitlab.config.gitlab.https && Gitlab.config.gitlab.port == 443
headers['Strict-Transport-Security'] = 'max-age=31536000'
end
end
@@ -167,7 +152,7 @@ class ApplicationController < ActionController::Base
def check_password_expiration
if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now && !current_user.ldap_user?
- redirect_to new_profile_password_path and return
+ return redirect_to new_profile_password_path
end
end
@@ -196,7 +181,7 @@ class ApplicationController < ActionController::Base
end
def gitlab_ldap_access(&block)
- Gitlab::LDAP::Access.open { |access| block.call(access) }
+ Gitlab::LDAP::Access.open { |access| yield(access) }
end
# JSON for infinite scroll via Pager object
@@ -233,7 +218,7 @@ class ApplicationController < ActionController::Base
def require_email
if current_user && current_user.temp_oauth_email? && session[:impersonator_id].nil?
- redirect_to profile_path, notice: 'Please complete your profile with email address' and return
+ return redirect_to profile_path, notice: 'Please complete your profile with email address'
end
end
diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb
index 6286d67d30c..2fe03020d2d 100644
--- a/app/controllers/concerns/creates_commit.rb
+++ b/app/controllers/concerns/creates_commit.rb
@@ -101,25 +101,18 @@ module CreatesCommit
# TODO: We should really clean this up
def set_commit_variables
- if can?(current_user, :push_code, @project)
- # Edit file in this project
- @mr_source_project = @project
-
- if @project.forked?
- # Merge request from this project to fork origin
- @mr_target_project = @project.forked_from_project
- @mr_target_branch = @mr_target_project.repository.root_ref
+ @mr_source_project =
+ if can?(current_user, :push_code, @project)
+ # Edit file in this project
+ @project
else
- # Merge request to this project
- @mr_target_project = @project
- @mr_target_branch = @ref || @target_branch
+ # Merge request from fork to this project
+ current_user.fork_of(@project)
end
- else
- # Merge request from fork to this project
- @mr_source_project = current_user.fork_of(@project)
- @mr_target_project = @project
- @mr_target_branch = @ref || @target_branch
- end
+
+ # Merge request to this project
+ @mr_target_project = @project
+ @mr_target_branch = @ref || @target_branch
@mr_source_branch = guess_mr_source_branch
end
diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb
index 6247934f81e..85ae4985e58 100644
--- a/app/controllers/concerns/issuable_collections.rb
+++ b/app/controllers/concerns/issuable_collections.rb
@@ -9,6 +9,36 @@ module IssuableCollections
private
+ def issuable_meta_data(issuable_collection, collection_type)
+ # map has to be used here since using pluck or select will
+ # throw an error when ordering issuables by priority which inserts
+ # a new order into the collection.
+ # We cannot use reorder to not mess up the paginated collection.
+ issuable_ids = issuable_collection.map(&:id)
+ issuable_note_count = Note.count_for_collection(issuable_ids, @collection_type)
+ issuable_votes_count = AwardEmoji.votes_for_collection(issuable_ids, @collection_type)
+ issuable_merge_requests_count =
+ if collection_type == 'Issue'
+ MergeRequestsClosingIssues.count_for_collection(issuable_ids)
+ else
+ []
+ end
+
+ issuable_ids.each_with_object({}) do |id, issuable_meta|
+ downvotes = issuable_votes_count.find { |votes| votes.awardable_id == id && votes.downvote? }
+ upvotes = issuable_votes_count.find { |votes| votes.awardable_id == id && votes.upvote? }
+ notes = issuable_note_count.find { |notes| notes.noteable_id == id }
+ merge_requests = issuable_merge_requests_count.find { |mr| mr.first == id }
+
+ issuable_meta[id] = Issuable::IssuableMeta.new(
+ upvotes.try(:count).to_i,
+ downvotes.try(:count).to_i,
+ notes.try(:count).to_i,
+ merge_requests.try(:last).to_i
+ )
+ end
+ end
+
def issues_collection
issues_finder.execute.preload(:project, :author, :assignee, :labels, :milestone, project: :namespace)
end
diff --git a/app/controllers/concerns/issues_action.rb b/app/controllers/concerns/issues_action.rb
index b46adcceb60..b17c138d5c7 100644
--- a/app/controllers/concerns/issues_action.rb
+++ b/app/controllers/concerns/issues_action.rb
@@ -9,6 +9,9 @@ module IssuesAction
.non_archived
.page(params[:page])
+ @collection_type = "Issue"
+ @issuable_meta_data = issuable_meta_data(@issues, @collection_type)
+
respond_to do |format|
format.html
format.atom { render layout: false }
diff --git a/app/controllers/concerns/merge_requests_action.rb b/app/controllers/concerns/merge_requests_action.rb
index fdb05bb3228..d3c8e4888bc 100644
--- a/app/controllers/concerns/merge_requests_action.rb
+++ b/app/controllers/concerns/merge_requests_action.rb
@@ -7,6 +7,9 @@ module MergeRequestsAction
@merge_requests = merge_requests_collection
.page(params[:page])
+
+ @collection_type = "MergeRequest"
+ @issuable_meta_data = issuable_meta_data(@merge_requests, @collection_type)
end
private
diff --git a/app/controllers/concerns/service_params.rb b/app/controllers/concerns/service_params.rb
index d7f5a4e4682..e610ccaec96 100644
--- a/app/controllers/concerns/service_params.rb
+++ b/app/controllers/concerns/service_params.rb
@@ -59,10 +59,10 @@ module ServiceParams
:user_key,
:username,
:webhook
- ]
+ ].freeze
# Parameters to ignore if no value is specified
- FILTER_BLANK_PARAMS = [:password]
+ FILTER_BLANK_PARAMS = [:password].freeze
def service_params
dynamic_params = @service.event_channel_names + @service.event_names
diff --git a/app/controllers/concerns/snippets_actions.rb b/app/controllers/concerns/snippets_actions.rb
new file mode 100644
index 00000000000..ca6dffe1cc5
--- /dev/null
+++ b/app/controllers/concerns/snippets_actions.rb
@@ -0,0 +1,21 @@
+module SnippetsActions
+ extend ActiveSupport::Concern
+
+ def edit
+ end
+
+ def raw
+ send_data(
+ convert_line_endings(@snippet.content),
+ type: 'text/plain; charset=utf-8',
+ disposition: 'inline',
+ filename: @snippet.sanitized_file_name
+ )
+ end
+
+ private
+
+ def convert_line_endings(content)
+ params[:line_ending] == 'raw' ? content : content.gsub(/\r\n/, "\n")
+ end
+end
diff --git a/app/controllers/concerns/spammable_actions.rb b/app/controllers/concerns/spammable_actions.rb
index a6891149bfa..d0a692070d9 100644
--- a/app/controllers/concerns/spammable_actions.rb
+++ b/app/controllers/concerns/spammable_actions.rb
@@ -17,13 +17,31 @@ module SpammableActions
private
- def recaptcha_params
- return {} unless params[:recaptcha_verification] && Gitlab::Recaptcha.load_configurations! && verify_recaptcha
+ def recaptcha_check_with_fallback(&fallback)
+ if spammable.valid?
+ redirect_to spammable
+ elsif render_recaptcha?
+ if params[:recaptcha_verification]
+ flash[:alert] = 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
+ end
+
+ render :verify
+ else
+ yield
+ end
+ end
+
+ def spammable_params
+ default_params = { request: request }
+
+ recaptcha_check = params[:recaptcha_verification] &&
+ Gitlab::Recaptcha.load_configurations! &&
+ verify_recaptcha
+
+ return default_params unless recaptcha_check
- {
- recaptcha_verified: true,
- spam_log_id: params[:spam_log_id]
- }
+ { recaptcha_verified: true,
+ spam_log_id: params[:spam_log_id] }.merge(default_params)
end
def spammable
diff --git a/app/controllers/dashboard/groups_controller.rb b/app/controllers/dashboard/groups_controller.rb
index de6bc689bb7..0b7cf8167f0 100644
--- a/app/controllers/dashboard/groups_controller.rb
+++ b/app/controllers/dashboard/groups_controller.rb
@@ -1,5 +1,5 @@
class Dashboard::GroupsController < Dashboard::ApplicationController
def index
- @group_members = current_user.group_members.includes(:source).page(params[:page])
+ @group_members = current_user.group_members.includes(source: :route).page(params[:page])
end
end
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index 3ba8c2f8bb9..325ae565537 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -1,19 +1,14 @@
class Dashboard::ProjectsController < Dashboard::ApplicationController
include FilterProjects
- before_action :event_filter
-
def index
- @projects = current_user.authorized_projects.sorted_by_activity
- @projects = filter_projects(@projects)
- @projects = @projects.includes(:namespace)
+ @projects = load_projects(current_user.authorized_projects)
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page])
respond_to do |format|
format.html { @last_push = current_user.recent_push }
format.atom do
- event_filter
load_events
render layout: false
end
@@ -26,9 +21,8 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
end
def starred
- @projects = current_user.viewable_starred_projects.sorted_by_activity
- @projects = filter_projects(@projects)
- @projects = @projects.includes(:namespace, :forked_from_project, :tags)
+ @projects = load_projects(current_user.viewable_starred_projects)
+ @projects = @projects.includes(:forked_from_project, :tags)
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page])
@@ -37,7 +31,6 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
respond_to do |format|
format.html
-
format.json do
render json: {
html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
@@ -48,9 +41,15 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
private
+ def load_projects(base_scope)
+ projects = base_scope.sorted_by_activity.includes(:namespace)
+
+ filter_projects(projects)
+ end
+
def load_events
- @events = Event.in_projects(@projects)
- @events = @event_filter.apply_filter(@events).with_associations
+ @events = Event.in_projects(load_projects(current_user.authorized_projects))
+ @events = event_filter.apply_filter(@events).with_associations
@events = @events.limit(20).offset(params[:offset] || 0)
end
end
diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb
index e3933e3d7b1..5848ca62777 100644
--- a/app/controllers/dashboard/todos_controller.rb
+++ b/app/controllers/dashboard/todos_controller.rb
@@ -1,4 +1,6 @@
class Dashboard::TodosController < Dashboard::ApplicationController
+ include ActionView::Helpers::NumberHelper
+
before_action :find_todos, only: [:index, :destroy_all]
def index
@@ -29,6 +31,17 @@ class Dashboard::TodosController < Dashboard::ApplicationController
end
end
+ def restore
+ TodoService.new.mark_todos_as_pending_by_ids([params[:id]], current_user)
+
+ render json: todos_counts
+ end
+
+ # Used in TodosHelper also
+ def self.todos_count_format(count)
+ count >= 100 ? '99+' : count
+ end
+
private
def find_todos
@@ -37,8 +50,8 @@ class Dashboard::TodosController < Dashboard::ApplicationController
def todos_counts
{
- count: current_user.todos_pending_count,
- done_count: current_user.todos_done_count
+ count: number_with_delimiter(current_user.todos_pending_count),
+ done_count: number_with_delimiter(current_user.todos_done_count)
}
end
end
diff --git a/app/controllers/explore/application_controller.rb b/app/controllers/explore/application_controller.rb
index a1ab8b99048..baf54520b9c 100644
--- a/app/controllers/explore/application_controller.rb
+++ b/app/controllers/explore/application_controller.rb
@@ -1,5 +1,5 @@
class Explore::ApplicationController < ApplicationController
- skip_before_action :authenticate_user!, :reject_blocked!
+ skip_before_action :authenticate_user!
layout 'explore'
end
diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb
index 4f273a8d4f0..0cbf3eb58a3 100644
--- a/app/controllers/groups/group_members_controller.rb
+++ b/app/controllers/groups/group_members_controller.rb
@@ -9,7 +9,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
@sort = params[:sort].presence || sort_value_name
@project = @group.projects.find(params[:project_id]) if params[:project_id]
- @members = @group.group_members
+ @members = GroupMembersFinder.new(@group).execute
@members = @members.non_invite unless can?(current_user, :admin_group, @group)
@members = @members.search(params[:search]) if params[:search].present?
@members = @members.sort(@sort)
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 264b14713fb..7ed54479599 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -13,9 +13,11 @@ class GroupsController < Groups::ApplicationController
before_action :authorize_create_group!, only: [:new, :create]
# Load group projects
- before_action :group_projects, only: [:show, :projects, :activity, :issues, :merge_requests]
+ before_action :group_projects, only: [:projects, :activity, :issues, :merge_requests]
before_action :event_filter, only: [:activity]
+ before_action :user_actions, only: [:show, :subgroups]
+
layout :determine_layout
def index
@@ -37,13 +39,6 @@ class GroupsController < Groups::ApplicationController
end
def show
- if current_user
- @last_push = current_user.recent_push
- @notification_setting = current_user.notification_settings_for(group)
- end
-
- @nested_groups = group.children
-
setup_projects
respond_to do |format|
@@ -62,6 +57,11 @@ class GroupsController < Groups::ApplicationController
end
end
+ def subgroups
+ @nested_groups = group.children
+ @nested_groups = @nested_groups.search(params[:filter_groups]) if params[:filter_groups].present?
+ end
+
def activity
respond_to do |format|
format.html
@@ -91,7 +91,7 @@ class GroupsController < Groups::ApplicationController
end
def destroy
- DestroyGroupService.new(@group, current_user).async_execute
+ Groups::DestroyService.new(@group, current_user).async_execute
redirect_to root_path, alert: "Group '#{@group.name}' was scheduled for deletion."
end
@@ -99,13 +99,16 @@ class GroupsController < Groups::ApplicationController
protected
def setup_projects
+ options = {}
+ options[:only_owned] = true if params[:shared] == '0'
+ options[:only_shared] = true if params[:shared] == '1'
+
+ @projects = GroupProjectsFinder.new(group, options).execute(current_user)
@projects = @projects.includes(:namespace)
@projects = @projects.sorted_by_activity
@projects = filter_projects(@projects)
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page]) if params[:filter_projects].blank?
-
- @shared_projects = GroupProjectsFinder.new(group, only_shared: true).execute(current_user)
end
def authorize_create_group!
@@ -138,7 +141,8 @@ class GroupsController < Groups::ApplicationController
:public,
:request_access_enabled,
:share_with_group_lock,
- :visibility_level
+ :visibility_level,
+ :parent_id
]
end
@@ -147,4 +151,11 @@ class GroupsController < Groups::ApplicationController
@events = event_filter.apply_filter(@events).with_associations
@events = @events.limit(20).offset(params[:offset] || 0)
end
+
+ def user_actions
+ if current_user
+ @last_push = current_user.recent_push
+ @notification_setting = current_user.notification_settings_for(group)
+ end
+ end
end
diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb
index 37feff79999..87c0f8905ff 100644
--- a/app/controllers/help_controller.rb
+++ b/app/controllers/help_controller.rb
@@ -1,5 +1,5 @@
class HelpController < ApplicationController
- skip_before_action :authenticate_user!, :reject_blocked!
+ skip_before_action :authenticate_user!
layout 'help'
diff --git a/app/controllers/import/fogbugz_controller.rb b/app/controllers/import/fogbugz_controller.rb
index 99b10b2f9b3..5df6bd34185 100644
--- a/app/controllers/import/fogbugz_controller.rb
+++ b/app/controllers/import/fogbugz_controller.rb
@@ -29,7 +29,7 @@ class Import::FogbugzController < Import::BaseController
unless user_map.is_a?(Hash) && user_map.all? { |k, v| !v[:name].blank? }
flash.now[:alert] = 'All users must have a name.'
- render 'new_user_map' and return
+ return render 'new_user_map'
end
session[:fogbugz_user_map] = user_map
diff --git a/app/controllers/import/google_code_controller.rb b/app/controllers/import/google_code_controller.rb
index 8d0de158f98..7d7f13ce5d5 100644
--- a/app/controllers/import/google_code_controller.rb
+++ b/app/controllers/import/google_code_controller.rb
@@ -44,13 +44,13 @@ class Import::GoogleCodeController < Import::BaseController
rescue
flash.now[:alert] = "The entered user map is not a valid JSON user map."
- render "new_user_map" and return
+ return render "new_user_map"
end
unless user_map.is_a?(Hash) && user_map.all? { |k, v| k.is_a?(String) && v.is_a?(String) }
flash.now[:alert] = "The entered user map is not a valid JSON user map."
- render "new_user_map" and return
+ return render "new_user_map"
end
# This is the default, so let's not save it into the database.
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index 58964a0e65d..7625187c7be 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -42,9 +42,7 @@ class InvitesController < ApplicationController
@token = params[:id]
@member = Member.find_by_invite_token(@token)
- unless @member
- render_404 and return
- end
+ return render_404 unless @member
@member
end
diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb
index c2e4d62b50b..3109439b2ff 100644
--- a/app/controllers/jwt_controller.rb
+++ b/app/controllers/jwt_controller.rb
@@ -5,7 +5,7 @@ class JwtController < ApplicationController
SERVICES = {
Auth::ContainerRegistryAuthenticationService::AUDIENCE => Auth::ContainerRegistryAuthenticationService,
- }
+ }.freeze
def auth
service = SERVICES[params[:service]]
@@ -39,7 +39,8 @@ class JwtController < ApplicationController
message: "HTTP Basic: Access denied\n" \
"You have 2FA enabled, please use a personal access token for Git over HTTP.\n" \
"You can generate one at #{profile_personal_access_tokens_url}" }
- ] }, status: 401
+ ]
+ }, status: 401
end
def render_unauthorized
@@ -47,7 +48,8 @@ class JwtController < ApplicationController
errors: [
{ code: 'UNAUTHORIZED',
message: 'HTTP Basic: Access denied' }
- ] }, status: 401
+ ]
+ }, status: 401
end
def auth_params
diff --git a/app/controllers/koding_controller.rb b/app/controllers/koding_controller.rb
index f3759b4c0ea..6b1e64ce819 100644
--- a/app/controllers/koding_controller.rb
+++ b/app/controllers/koding_controller.rb
@@ -1,5 +1,5 @@
class KodingController < ApplicationController
- before_action :check_integration!, :authenticate_user!, :reject_blocked!
+ before_action :check_integration!
layout 'koding'
def index
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index f54c79c2e37..58d50ad647b 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -78,6 +78,13 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
handle_omniauth
end
+ def authentiq
+ if params['sid']
+ handle_service_ticket oauth['provider'], params['sid']
+ end
+ handle_omniauth
+ end
+
private
def handle_omniauth
@@ -115,7 +122,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
else
error_message = @user.errors.full_messages.to_sentence
- redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return
+ return redirect_to omniauth_error_path(oauth['provider'], error: error_message)
end
end
diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb
index 830e0b9591b..c8663a3c38e 100644
--- a/app/controllers/profiles/keys_controller.rb
+++ b/app/controllers/profiles/keys_controller.rb
@@ -45,13 +45,13 @@ class Profiles::KeysController < Profiles::ApplicationController
if user.present?
render text: user.all_ssh_keys.join("\n"), content_type: "text/plain"
else
- render_404 and return
+ return render_404
end
rescue => e
render text: e.message
end
else
- render_404 and return
+ return render_404
end
end
diff --git a/app/controllers/profiles/notifications_controller.rb b/app/controllers/profiles/notifications_controller.rb
index b8b71d295f6..a271e2dfc4b 100644
--- a/app/controllers/profiles/notifications_controller.rb
+++ b/app/controllers/profiles/notifications_controller.rb
@@ -17,6 +17,6 @@ class Profiles::NotificationsController < Profiles::ApplicationController
end
def user_params
- params.require(:user).permit(:notification_email)
+ params.require(:user).permit(:notification_email, :notified_of_own_activity)
end
end
diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb
index a9a06ecc808..0d891ef4004 100644
--- a/app/controllers/profiles/preferences_controller.rb
+++ b/app/controllers/profiles/preferences_controller.rb
@@ -34,7 +34,6 @@ class Profiles::PreferencesController < Profiles::ApplicationController
:layout,
:dashboard,
:project_view,
- :theme_id
)
end
end
diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb
index 18044ca78e2..26e7e93533e 100644
--- a/app/controllers/profiles/two_factor_auths_controller.rb
+++ b/app/controllers/profiles/two_factor_auths_controller.rb
@@ -80,7 +80,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
def build_qr_code
uri = current_user.otp_provisioning_uri(account_string, issuer: issuer_host)
- RQRCode::render_qrcode(uri, :svg, level: :m, unit: 3)
+ RQRCode.render_qrcode(uri, :svg, level: :m, unit: 3)
end
def account_string
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index db33b60b229..e2f81b09adc 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -83,7 +83,6 @@ class Projects::ApplicationController < ApplicationController
end
def apply_diff_view_cookie!
- @show_changes_tab = params[:view].present?
cookies.permanent[:diff_view] = params.delete(:view) if params[:view].present?
end
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 4c39fe98028..39ba815cfca 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -61,10 +61,10 @@ class Projects::BlobController < Projects::ApplicationController
end
def destroy
- create_commit(Files::DeleteService, success_notice: "The file has been successfully deleted.",
- success_path: namespace_project_tree_path(@project.namespace, @project, @target_branch),
- failure_view: :show,
- failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
+ create_commit(Files::DestroyService, success_notice: "The file has been successfully deleted.",
+ success_path: namespace_project_tree_path(@project.namespace, @project, @target_branch),
+ failure_view: :show,
+ failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
end
def diff
@@ -95,7 +95,7 @@ class Projects::BlobController < Projects::ApplicationController
else
if tree = @repository.tree(@commit.id, @path)
if tree.entries.any?
- redirect_to namespace_project_tree_path(@project.namespace, @project, File.join(@ref, @path)) and return
+ return redirect_to namespace_project_tree_path(@project.namespace, @project, File.join(@ref, @path))
end
end
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index 89d84809e3a..a01c0caa959 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -1,8 +1,9 @@
class Projects::BranchesController < Projects::ApplicationController
include ActionView::Helpers::SanitizeHelper
include SortingHelper
+
# Authorize
- before_action :require_non_empty_project
+ before_action :require_non_empty_project, except: :create
before_action :authorize_download_code!
before_action :authorize_push_code!, only: [:new, :create, :destroy, :destroy_all_merged]
@@ -32,6 +33,8 @@ class Projects::BranchesController < Projects::ApplicationController
branch_name = sanitize(strip_tags(params[:branch_name]))
branch_name = Addressable::URI.unescape(branch_name)
+ redirect_to_autodeploy = project.empty_repo? && project.deployment_services.present?
+
result = CreateBranchService.new(project, current_user).
execute(branch_name, ref)
@@ -42,8 +45,15 @@ class Projects::BranchesController < Projects::ApplicationController
if result[:status] == :success
@branch = result[:branch]
- redirect_to namespace_project_tree_path(@project.namespace, @project,
- @branch.name)
+
+ if redirect_to_autodeploy
+ redirect_to(
+ url_to_autodeploy_setup(project, branch_name),
+ notice: view_context.autodeploy_flash_notice(branch_name))
+ else
+ redirect_to namespace_project_tree_path(@project.namespace, @project,
+ @branch.name)
+ end
else
@error = result[:message]
render action: 'new'
@@ -76,7 +86,19 @@ class Projects::BranchesController < Projects::ApplicationController
ref_escaped = sanitize(strip_tags(params[:ref]))
Addressable::URI.unescape(ref_escaped)
else
- @project.default_branch
+ @project.default_branch || 'master'
end
end
+
+ def url_to_autodeploy_setup(project, branch_name)
+ namespace_project_new_blob_path(
+ project.namespace,
+ project,
+ branch_name,
+ file_name: '.gitlab-ci.yml',
+ commit_message: 'Set up auto deploy',
+ target_branch: branch_name,
+ context: 'autodeploy'
+ )
+ end
end
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index 0ec8f5bd64a..fed75396d6e 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -9,15 +9,40 @@ class Projects::EnvironmentsController < Projects::ApplicationController
before_action :verify_api_request!, only: :terminal_websocket_authorize
def index
- @scope = params[:scope]
- @environments = project.environments.includes(:last_deployment)
+ @environments = project.environments
+ .with_state(params[:scope] || :available)
respond_to do |format|
format.html
format.json do
- render json: EnvironmentSerializer
- .new(project: @project, user: current_user)
- .represent(@environments)
+ render json: {
+ environments: EnvironmentSerializer
+ .new(project: @project, user: @current_user)
+ .with_pagination(request, response)
+ .within_folders
+ .represent(@environments),
+ available_count: project.environments.available.count,
+ stopped_count: project.environments.stopped.count
+ }
+ end
+ end
+ end
+
+ def folder
+ folder_environments = project.environments.where(environment_type: params[:id])
+ @environments = folder_environments.with_state(params[:scope] || :available)
+
+ respond_to do |format|
+ format.html
+ format.json do
+ render json: {
+ environments: EnvironmentSerializer
+ .new(project: @project, user: @current_user)
+ .with_pagination(request, response)
+ .represent(@environments),
+ available_count: folder_environments.available.count,
+ stopped_count: folder_environments.stopped.count
+ }
end
end
end
diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb
index 216c158e41e..9a1bf037a95 100644
--- a/app/controllers/projects/git_http_client_controller.rb
+++ b/app/controllers/projects/git_http_client_controller.rb
@@ -76,11 +76,12 @@ class Projects::GitHttpClientController < Projects::ApplicationController
return @project if defined?(@project)
project_id, _ = project_id_with_suffix
- if project_id.blank?
- @project = nil
- else
- @project = Project.find_by_full_path("#{params[:namespace_id]}/#{project_id}")
- end
+ @project =
+ if project_id.blank?
+ nil
+ else
+ Project.find_by_full_path("#{params[:namespace_id]}/#{project_id}")
+ end
end
# This method returns two values so that we can parse
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index c75b8987a4b..ca5e81100da 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -23,8 +23,11 @@ class Projects::IssuesController < Projects::ApplicationController
respond_to :html
def index
- @issues = issues_collection
- @issues = @issues.page(params[:page])
+ @collection_type = "Issue"
+ @issues = issues_collection
+ @issues = @issues.page(params[:page])
+ @issuable_meta_data = issuable_meta_data(@issues, @collection_type)
+
if @issues.out_of_range? && @issues.total_pages != 0
return redirect_to url_for(params.merge(page: @issues.total_pages))
end
@@ -91,15 +94,15 @@ class Projects::IssuesController < Projects::ApplicationController
end
def create
- extra_params = { request: request,
- merge_request_for_resolving_discussions: merge_request_for_resolving_discussions }
- extra_params.merge!(recaptcha_params)
+ create_params = issue_params
+ .merge(merge_request_for_resolving_discussions: merge_request_for_resolving_discussions)
+ .merge(spammable_params)
- @issue = Issues::CreateService.new(project, current_user, issue_params.merge(extra_params)).execute
+ @issue = Issues::CreateService.new(project, current_user, create_params).execute
respond_to do |format|
format.html do
- html_response_create
+ recaptcha_check_with_fallback { render :new }
end
format.js do
@link = @issue.attachment.url.to_js
@@ -108,7 +111,9 @@ class Projects::IssuesController < Projects::ApplicationController
end
def update
- @issue = Issues::UpdateService.new(project, current_user, issue_params).execute(issue)
+ update_params = issue_params.merge(spammable_params)
+
+ @issue = Issues::UpdateService.new(project, current_user, update_params).execute(issue)
if params[:move_to_project_id].to_i > 0
new_project = Project.find(params[:move_to_project_id])
@@ -120,11 +125,7 @@ class Projects::IssuesController < Projects::ApplicationController
respond_to do |format|
format.html do
- if @issue.valid?
- redirect_to issue_path(@issue)
- else
- render :edit
- end
+ recaptcha_check_with_fallback { render :edit }
end
format.json do
@@ -176,20 +177,6 @@ class Projects::IssuesController < Projects::ApplicationController
protected
- def html_response_create
- if @issue.valid?
- redirect_to issue_path(@issue)
- elsif render_recaptcha?
- if params[:recaptcha_verification]
- flash[:alert] = 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
- end
-
- render :verify
- else
- render :new
- end
- end
-
def issue
# The Sortable default scope causes performance issues when used with find_by
@noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take || redirect_old
diff --git a/app/controllers/projects/lfs_api_controller.rb b/app/controllers/projects/lfs_api_controller.rb
index 440259b643c..8a5a645ed0e 100644
--- a/app/controllers/projects/lfs_api_controller.rb
+++ b/app/controllers/projects/lfs_api_controller.rb
@@ -48,6 +48,10 @@ class Projects::LfsApiController < Projects::GitHttpClientController
objects.each do |object|
if existing_oids.include?(object[:oid])
object[:actions] = download_actions(object)
+
+ if Guest.can?(:download_code, project)
+ object[:authenticated] = true
+ end
else
object[:error] = {
code: 404,
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index fbad66c5c40..d122c7fdcb2 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -36,8 +36,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
before_action :authorize_can_resolve_conflicts!, only: [:conflicts, :conflict_for_path, :resolve_conflicts]
def index
- @merge_requests = merge_requests_collection
- @merge_requests = @merge_requests.page(params[:page])
+ @collection_type = "MergeRequest"
+ @merge_requests = merge_requests_collection
+ @merge_requests = @merge_requests.page(params[:page])
+ @issuable_meta_data = issuable_meta_data(@merge_requests, @collection_type)
+
if @merge_requests.out_of_range? && @merge_requests.total_pages != 0
return redirect_to url_for(params.merge(page: @merge_requests.total_pages))
end
@@ -47,6 +50,17 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@labels = LabelsFinder.new(current_user, labels_params).execute
end
+ @users = []
+ if params[:assignee_id].present?
+ assignee = User.find_by_id(params[:assignee_id])
+ @users.push(assignee) if assignee
+ end
+
+ if params[:author_id].present?
+ author = User.find_by_id(params[:author_id])
+ @users.push(author) if author
+ end
+
respond_to do |format|
format.html
format.json do
@@ -242,6 +256,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
respond_to do |format|
format.html do
define_new_vars
+ @show_changes_tab = true
render "new"
end
format.json do
@@ -366,11 +381,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def merge_widget_refresh
- if merge_request.in_progress_merge_commit_sha || merge_request.state == 'merged'
- @status = :success
- elsif merge_request.merge_when_build_succeeds
- @status = :merge_when_build_succeeds
- end
+ @status =
+ if merge_request.merge_when_build_succeeds
+ :merge_when_build_succeeds
+ else
+ # Only MRs that can be merged end in this action
+ # MR can be already picked up for merge / merged already or can be waiting for worker to be picked up
+ # in last case it does not have any special status. Possible error is handled inside widget js function
+ :success
+ end
render 'merge'
end
@@ -610,6 +629,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@labels = LabelsFinder.new(current_user, project_id: @project.id).execute
+ @show_changes_tab = params[:show_changes].present?
+
define_pipelines_vars
end
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index c5d93ce25bc..b033f7b5ea9 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -51,7 +51,7 @@ class Projects::NotesController < Projects::ApplicationController
def destroy
if note.editable?
- Notes::DeleteService.new(project, current_user).execute(note)
+ Notes::DestroyService.new(project, current_user).execute(note)
end
respond_to do |format|
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index 84451257b98..8657bc4dfdc 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -13,9 +13,15 @@ class Projects::PipelinesController < Projects::ApplicationController
.page(params[:page])
.per(30)
- @running_or_pending_count = PipelinesFinder
+ @running_count = PipelinesFinder
.new(project).execute(scope: 'running').count
+ @pending_count = PipelinesFinder
+ .new(project).execute(scope: 'pending').count
+
+ @finished_count = PipelinesFinder
+ .new(project).execute(scope: 'finished').count
+
@pipelines_count = PipelinesFinder
.new(project).execute.count
@@ -29,7 +35,9 @@ class Projects::PipelinesController < Projects::ApplicationController
.represent(@pipelines),
count: {
all: @pipelines_count,
- running_or_pending: @running_or_pending_count
+ running: @running_count,
+ pending: @pending_count,
+ finished: @finished_count,
}
}
end
diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb
index 9a438d5512c..2f422d352ed 100644
--- a/app/controllers/projects/protected_branches_controller.rb
+++ b/app/controllers/projects/protected_branches_controller.rb
@@ -68,8 +68,12 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
def access_levels_options
{
- push_access_levels: ProtectedBranch::PushAccessLevel.human_access_levels.map { |id, text| { id: id, text: text, before_divider: true } },
- merge_access_levels: ProtectedBranch::MergeAccessLevel.human_access_levels.map { |id, text| { id: id, text: text, before_divider: true } }
+ push_access_levels: {
+ "Roles" => ProtectedBranch::PushAccessLevel.human_access_levels.map { |id, text| { id: id, text: text, before_divider: true } },
+ },
+ merge_access_levels: {
+ "Roles" => ProtectedBranch::MergeAccessLevel.human_access_levels.map { |id, text| { id: id, text: text, before_divider: true } }
+ }
}
end
diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb
index 74c54037ba9..8b50ea207a5 100644
--- a/app/controllers/projects/runners_controller.rb
+++ b/app/controllers/projects/runners_controller.rb
@@ -12,7 +12,7 @@ class Projects::RunnersController < Projects::ApplicationController
end
def update
- if @runner.update_attributes(runner_params)
+ if Ci::UpdateRunnerService.new(@runner).update(runner_params)
redirect_to runner_path(@runner), notice: 'Runner was successfully updated.'
else
render 'edit'
@@ -28,7 +28,7 @@ class Projects::RunnersController < Projects::ApplicationController
end
def resume
- if @runner.update_attributes(active: true)
+ if Ci::UpdateRunnerService.new(@runner).update(active: true)
redirect_to runner_path(@runner), notice: 'Runner was successfully updated.'
else
redirect_to runner_path(@runner), alert: 'Runner was not updated.'
@@ -36,7 +36,7 @@ class Projects::RunnersController < Projects::ApplicationController
end
def pause
- if @runner.update_attributes(active: false)
+ if Ci::UpdateRunnerService.new(@runner).update(active: false)
redirect_to runner_path(@runner), notice: 'Runner was successfully updated.'
else
redirect_to runner_path(@runner), alert: 'Runner was not updated.'
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index 5d193f26a8e..ea1a97b7cf0 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -1,6 +1,7 @@
class Projects::SnippetsController < Projects::ApplicationController
include ToggleAwardEmoji
include SpammableActions
+ include SnippetsActions
before_action :module_enabled
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam]
@@ -37,27 +38,19 @@ class Projects::SnippetsController < Projects::ApplicationController
end
def create
- create_params = snippet_params.merge(request: request)
- @snippet = CreateSnippetService.new(@project, current_user, create_params).execute
+ create_params = snippet_params.merge(spammable_params)
- if @snippet.valid?
- respond_with(@snippet,
- location: namespace_project_snippet_path(@project.namespace,
- @project, @snippet))
- else
- render :new
- end
- end
+ @snippet = CreateSnippetService.new(@project, current_user, create_params).execute
- def edit
+ recaptcha_check_with_fallback { render :new }
end
def update
- UpdateSnippetService.new(project, current_user, @snippet,
- snippet_params).execute
- respond_with(@snippet,
- location: namespace_project_snippet_path(@project.namespace,
- @project, @snippet))
+ update_params = snippet_params.merge(spammable_params)
+
+ UpdateSnippetService.new(project, current_user, @snippet, update_params).execute
+
+ recaptcha_check_with_fallback { render :edit }
end
def show
@@ -74,15 +67,6 @@ class Projects::SnippetsController < Projects::ApplicationController
redirect_to namespace_project_snippets_path(@project.namespace, @project)
end
- def raw
- send_data(
- @snippet.content,
- type: 'text/plain; charset=utf-8',
- disposition: 'inline',
- filename: @snippet.sanitized_file_name
- )
- end
-
protected
def snippet
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index e2d9d5ed460..33379659d73 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -27,7 +27,7 @@ class Projects::TagsController < Projects::ApplicationController
end
def create
- result = CreateTagService.new(@project, current_user).
+ result = Tags::CreateService.new(@project, current_user).
execute(params[:tag_name], params[:ref], params[:message], params[:release_description])
if result[:status] == :success
@@ -41,7 +41,7 @@ class Projects::TagsController < Projects::ApplicationController
end
def destroy
- DeleteTagService.new(project, current_user).execute(params[:id])
+ Tags::DestroyService.new(project, current_user).execute(params[:id])
respond_to do |format|
format.html do
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index cb3ed0f6f9c..4f094146348 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -15,10 +15,10 @@ class Projects::TreeController < Projects::ApplicationController
if tree.entries.empty?
if @repository.blob_at(@commit.id, @path)
- redirect_to(
+ return redirect_to(
namespace_project_blob_path(@project.namespace, @project,
File.join(@ref, @path))
- ) and return
+ )
elsif @path.present?
return render_404
end
diff --git a/app/controllers/projects/uploads_controller.rb b/app/controllers/projects/uploads_controller.rb
index 50ba33ed570..61686499bd3 100644
--- a/app/controllers/projects/uploads_controller.rb
+++ b/app/controllers/projects/uploads_controller.rb
@@ -1,6 +1,6 @@
class Projects::UploadsController < Projects::ApplicationController
- skip_before_action :reject_blocked!, :project,
- :repository, if: -> { action_name == 'show' && image_or_video? }
+ skip_before_action :project, :repository,
+ if: -> { action_name == 'show' && image_or_video? }
before_action :authorize_upload_file!, only: [:create]
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index c3353446fd1..2d8064c9878 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -8,6 +8,7 @@ class Projects::WikisController < Projects::ApplicationController
def pages
@wiki_pages = Kaminari.paginate_array(@project_wiki.pages).page(params[:page])
+ @wiki_entries = WikiPage.group_by_directory(@wiki_pages)
end
def show
@@ -83,7 +84,7 @@ class Projects::WikisController < Projects::ApplicationController
def destroy
@page = @project_wiki.find_page(params[:id])
- @page.delete if @page
+ WikiPages::DestroyService.new(@project, current_user).execute(@page)
redirect_to(
namespace_project_wiki_path(@project.namespace, @project, :home),
@@ -116,7 +117,7 @@ class Projects::WikisController < Projects::ApplicationController
# Call #wiki to make sure the Wiki Repo is initialized
@project_wiki.wiki
- @sidebar_wiki_pages = @project_wiki.pages.first(15)
+ @sidebar_wiki_entries = WikiPage.group_by_directory(@project_wiki.pages.first(15))
rescue ProjectWiki::CouldNotCreateWikiError
flash[:notice] = "Could not create Wiki Repository at this time. Please try again later."
redirect_to project_path(@project)
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index 68bf01ba08d..b44f38d4a0c 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -24,7 +24,7 @@ class RegistrationsController < Devise::RegistrationsController
end
def destroy
- DeleteUserService.new(current_user).execute(current_user)
+ Users::DestroyService.new(current_user).execute(current_user)
respond_to do |format|
format.html do
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index 6576ebd5235..612d69cf557 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -1,5 +1,5 @@
class SearchController < ApplicationController
- skip_before_action :authenticate_user!, :reject_blocked!
+ skip_before_action :authenticate_user!
include SearchHelper
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 93a180b9036..7d81c96262f 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -15,11 +15,12 @@ class SessionsController < Devise::SessionsController
def new
set_minimum_password_length
- if Gitlab.config.ldap.enabled
- @ldap_servers = Gitlab::LDAP::Config.servers
- else
- @ldap_servers = []
- end
+ @ldap_servers =
+ if Gitlab.config.ldap.enabled
+ Gitlab::LDAP::Config.servers
+ else
+ []
+ end
super
end
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index b169d993688..f3fd3da8b20 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -1,6 +1,7 @@
class SnippetsController < ApplicationController
include ToggleAwardEmoji
include SpammableActions
+ include SnippetsActions
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :download]
@@ -22,13 +23,14 @@ class SnippetsController < ApplicationController
if params[:username].present?
@user = User.find_by(username: params[:username])
- render_404 and return unless @user
+ return render_404 unless @user
@snippets = SnippetsFinder.new.execute(current_user, {
filter: :by_user,
user: @user,
- scope: params[:scope] }).
- page(params[:page])
+ scope: params[:scope]
+ })
+ .page(params[:page])
render 'index'
else
@@ -41,19 +43,19 @@ class SnippetsController < ApplicationController
end
def create
- create_params = snippet_params.merge(request: request)
- @snippet = CreateSnippetService.new(nil, current_user, create_params).execute
+ create_params = snippet_params.merge(spammable_params)
- respond_with @snippet.becomes(Snippet)
- end
+ @snippet = CreateSnippetService.new(nil, current_user, create_params).execute
- def edit
+ recaptcha_check_with_fallback { render :new }
end
def update
- UpdateSnippetService.new(nil, current_user, @snippet,
- snippet_params).execute
- respond_with @snippet.becomes(Snippet)
+ update_params = snippet_params.merge(spammable_params)
+
+ UpdateSnippetService.new(nil, current_user, @snippet, update_params).execute
+
+ recaptcha_check_with_fallback { render :edit }
end
def show
@@ -67,18 +69,9 @@ class SnippetsController < ApplicationController
redirect_to snippets_path
end
- def raw
- send_data(
- @snippet.content,
- type: 'text/plain; charset=utf-8',
- disposition: 'inline',
- filename: @snippet.sanitized_file_name
- )
- end
-
def download
send_data(
- @snippet.content,
+ convert_line_endings(@snippet.content),
type: 'text/plain; charset=utf-8',
filename: @snippet.sanitized_file_name
)
diff --git a/app/finders/group_members_finder.rb b/app/finders/group_members_finder.rb
new file mode 100644
index 00000000000..9f2206346ce
--- /dev/null
+++ b/app/finders/group_members_finder.rb
@@ -0,0 +1,20 @@
+class GroupMembersFinder < Projects::ApplicationController
+ def initialize(group)
+ @group = group
+ end
+
+ def execute
+ group_members = @group.members
+
+ return group_members unless @group.parent
+
+ parents_members = GroupMember.non_request.
+ where(source_id: @group.ancestors.select(:id)).
+ where.not(user_id: @group.users.select(:id))
+
+ wheres = ["members.id IN (#{group_members.select(:id).to_sql})"]
+ wheres << "members.id IN (#{parents_members.select(:id).to_sql})"
+
+ GroupMember.where(wheres.join(' OR '))
+ end
+end
diff --git a/app/finders/groups_finder.rb b/app/finders/groups_finder.rb
index 4e43f42e9e1..d932a17883f 100644
--- a/app/finders/groups_finder.rb
+++ b/app/finders/groups_finder.rb
@@ -2,7 +2,7 @@ class GroupsFinder < UnionFinder
def execute(current_user = nil)
segments = all_groups(current_user)
- find_union(segments, Group).order_id_desc
+ find_union(segments, Group).with_route.order_id_desc
end
private
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 1576fc80a6b..f49301e2631 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -16,9 +16,10 @@
# label_name: string
# sort: string
# non_archived: boolean
+# iids: integer[]
#
class IssuableFinder
- NONE = '0'
+ NONE = '0'.freeze
attr_accessor :current_user, :params
@@ -40,6 +41,7 @@ class IssuableFinder
items = by_label(items)
items = by_due_date(items)
items = by_non_archived(items)
+ items = by_iids(items)
sort(items)
end
@@ -266,16 +268,11 @@ class IssuableFinder
end
def by_search(items)
- if search
- items =
- if search =~ iid_pattern
- items.where(iid: $~[:iid])
- else
- items.full_search(search)
- end
- end
+ search ? items.full_search(search) : items
+ end
- items
+ def by_iids(items)
+ params[:iids].present? ? items.where(iid: params[:iids]) : items
end
def sort(items)
diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb
index 707eddd4d29..f542f72a386 100644
--- a/app/finders/issues_finder.rb
+++ b/app/finders/issues_finder.rb
@@ -26,10 +26,6 @@ class IssuesFinder < IssuableFinder
IssuesFinder.not_restricted_by_confidentiality(current_user)
end
- def iid_pattern
- @iid_pattern ||= %r{\A#{Regexp.escape(Issue.reference_prefix)}(?<iid>\d+)\z}
- end
-
def self.not_restricted_by_confidentiality(user)
return Issue.where('issues.confidential IS NULL OR issues.confidential IS FALSE') if user.blank?
diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb
index 8b82255445e..b76ca389f38 100644
--- a/app/finders/merge_requests_finder.rb
+++ b/app/finders/merge_requests_finder.rb
@@ -20,14 +20,4 @@ class MergeRequestsFinder < IssuableFinder
def klass
MergeRequest
end
-
- private
-
- def iid_pattern
- @iid_pattern ||= %r{\A[
- #{Regexp.escape(MergeRequest.reference_prefix)}
- #{Regexp.escape(Issue.reference_prefix)}
- ](?<iid>\d+)\z
- }x
- end
end
diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb
index 4bd8c83081a..6630c6384f2 100644
--- a/app/finders/notes_finder.rb
+++ b/app/finders/notes_finder.rb
@@ -28,11 +28,12 @@ class NotesFinder
private
def init_collection
- if @params[:target_id]
- @notes = on_target(@params[:target_type], @params[:target_id])
- else
- @notes = notes_of_any_type
- end
+ @notes =
+ if @params[:target_id]
+ on_target(@params[:target_type], @params[:target_id])
+ else
+ notes_of_any_type
+ end
end
def notes_of_any_type
diff --git a/app/finders/pipelines_finder.rb b/app/finders/pipelines_finder.rb
index 32aea75486d..a9172f6767f 100644
--- a/app/finders/pipelines_finder.rb
+++ b/app/finders/pipelines_finder.rb
@@ -10,7 +10,11 @@ class PipelinesFinder
scoped_pipelines =
case scope
when 'running'
- pipelines.running_or_pending
+ pipelines.running
+ when 'pending'
+ pipelines.pending
+ when 'finished'
+ pipelines.finished
when 'branches'
from_ids(ids_for_ref(branches))
when 'tags'
diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb
index c7911736812..18ec45f300d 100644
--- a/app/finders/projects_finder.rb
+++ b/app/finders/projects_finder.rb
@@ -3,7 +3,7 @@ class ProjectsFinder < UnionFinder
segments = all_projects(current_user)
segments.map! { |s| s.where(id: project_ids_relation) } if project_ids_relation
- find_union(segments, Project)
+ find_union(segments, Project).with_route
end
private
diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb
index a93a63bdb9b..b7f091f334d 100644
--- a/app/finders/todos_finder.rb
+++ b/app/finders/todos_finder.rb
@@ -13,7 +13,7 @@
#
class TodosFinder
- NONE = '0'
+ NONE = '0'.freeze
attr_accessor :current_user, :params
@@ -99,7 +99,7 @@ class TodosFinder
end
def type?
- type.present? && ['Issue', 'MergeRequest'].include?(type)
+ type.present? && %w(Issue MergeRequest).include?(type)
end
def type
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index bee323993a0..70419eb4bde 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -69,11 +69,12 @@ module ApplicationHelper
end
def avatar_icon(user_or_email = nil, size = nil, scale = 2)
- if user_or_email.is_a?(User)
- user = user_or_email
- else
- user = User.find_by_any_email(user_or_email.try(:downcase))
- end
+ user =
+ if user_or_email.is_a?(User)
+ user_or_email
+ else
+ User.find_by_any_email(user_or_email.try(:downcase))
+ end
if user
user.avatar_url(size) || default_avatar
@@ -296,4 +297,13 @@ module ApplicationHelper
def page_class
"issue-boards-page" if current_controller?(:boards)
end
+
+ # Returns active css class when condition returns true
+ # otherwise returns nil.
+ #
+ # Example:
+ # %li{ class: active_when(params[:filter] == '1') }
+ def active_when(condition)
+ 'active' if condition
+ end
end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 60485160495..4b025669f69 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -1,28 +1,15 @@
module ApplicationSettingsHelper
- def gravatar_enabled?
- current_application_settings.gravatar_enabled?
- end
-
- def signup_enabled?
- current_application_settings.signup_enabled?
- end
-
- def signin_enabled?
- current_application_settings.signin_enabled?
- end
+ delegate :gravatar_enabled?,
+ :signup_enabled?,
+ :signin_enabled?,
+ :akismet_enabled?,
+ :koding_enabled?,
+ to: :current_application_settings
def user_oauth_applications?
current_application_settings.user_oauth_applications
end
- def askimet_enabled?
- current_application_settings.akismet_enabled?
- end
-
- def koding_enabled?
- current_application_settings.koding_enabled?
- end
-
def allowed_protocols_present?
current_application_settings.enabled_git_access_protocol.present?
end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 311a70725ab..7f32c1b5300 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -153,16 +153,17 @@ module BlobHelper
# Because we are opionated we set the cache headers ourselves.
response.cache_control[:public] = @project.public?
- if @ref && @commit && @ref == @commit.id
- # This is a link to a commit by its commit SHA. That means that the blob
- # is immutable. The only reason to invalidate the cache is if the commit
- # was deleted or if the user lost access to the repository.
- response.cache_control[:max_age] = Blob::CACHE_TIME_IMMUTABLE
- else
- # A branch or tag points at this blob. That means that the expected blob
- # value may change over time.
- response.cache_control[:max_age] = Blob::CACHE_TIME
- end
+ response.cache_control[:max_age] =
+ if @ref && @commit && @ref == @commit.id
+ # This is a link to a commit by its commit SHA. That means that the blob
+ # is immutable. The only reason to invalidate the cache is if the commit
+ # was deleted or if the user lost access to the repository.
+ Blob::CACHE_TIME_IMMUTABLE
+ else
+ # A branch or tag points at this blob. That means that the expected blob
+ # value may change over time.
+ Blob::CACHE_TIME
+ end
response.etag = @blob.id
!stale
diff --git a/app/helpers/builds_helper.rb b/app/helpers/builds_helper.rb
index 9fc69e12266..ff937b5ebd2 100644
--- a/app/helpers/builds_helper.rb
+++ b/app/helpers/builds_helper.rb
@@ -1,7 +1,7 @@
module BuildsHelper
def sidebar_build_class(build, current_build)
build_class = ''
- build_class += ' active' if build == current_build
+ build_class += ' active' if build.id === current_build.id
build_class += ' retried' if build.retried?
build_class
end
diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb
index 2843ad96efa..f927cfc998f 100644
--- a/app/helpers/emails_helper.rb
+++ b/app/helpers/emails_helper.rb
@@ -1,4 +1,6 @@
module EmailsHelper
+ include AppearancesHelper
+
# Google Actions
# https://developers.google.com/gmail/markup/reference/go-to-action
def email_action(url)
@@ -22,7 +24,7 @@ module EmailsHelper
def action_title(url)
return unless url
- ["merge_requests", "issues", "commit"].each do |action|
+ %w(merge_requests issues commit).each do |action|
if url.split("/").include?(action)
return "View #{action.humanize.singularize}"
end
@@ -49,4 +51,19 @@ module EmailsHelper
msg = "This link is valid for #{password_reset_token_valid_time}. "
msg << "After it expires, you can #{link_tag}."
end
+
+ def header_logo
+ if brand_item && brand_item.header_logo?
+ image_tag(
+ brand_item.header_logo,
+ style: 'height: 50px'
+ )
+ else
+ image_tag(
+ image_url('mailers/gitlab_header_logo.gif'),
+ size: "55x50",
+ alt: "GitLab"
+ )
+ end
+ end
end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 03354c235eb..860a665ae26 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -23,7 +23,7 @@ module IssuablesHelper
def issuable_json_path(issuable)
project = issuable.project
- if issuable.kind_of?(MergeRequest)
+ if issuable.is_a?(MergeRequest)
namespace_project_merge_request_path(project.namespace, project, issuable.iid, :json)
else
namespace_project_issue_path(project.namespace, project, issuable.iid, :json)
@@ -52,7 +52,7 @@ module IssuablesHelper
field_name: 'issuable_template',
selected: selected_template(issuable),
project_path: ref_project.path,
- namespace_path: ref_project.namespace.path
+ namespace_path: ref_project.namespace.full_path
}
}
@@ -198,7 +198,7 @@ module IssuablesHelper
@counts[issuable_type][state]
end
- IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page]
+ IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page].freeze
private_constant :IRRELEVANT_PARAMS_FOR_CACHE_KEY
def issuables_state_counter_cache_key(issuable_type, state)
diff --git a/app/helpers/javascript_helper.rb b/app/helpers/javascript_helper.rb
index 320dd89c9d3..68c09c922a6 100644
--- a/app/helpers/javascript_helper.rb
+++ b/app/helpers/javascript_helper.rb
@@ -2,6 +2,7 @@ module JavascriptHelper
def page_specific_javascript_tag(js)
javascript_include_tag asset_path(js)
end
+
def page_specific_javascript_bundle_tag(js)
javascript_include_tag(*webpack_asset_paths(js))
end
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index 91b24b8bc29..7d8505d704e 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -64,21 +64,21 @@ module MergeRequestsHelper
end
def mr_closes_issues
- @mr_closes_issues ||= @merge_request.closes_issues
+ @mr_closes_issues ||= @merge_request.closes_issues(current_user)
end
def mr_issues_mentioned_but_not_closing
- @mr_issues_mentioned_but_not_closing ||= @merge_request.issues_mentioned_but_not_closing
+ @mr_issues_mentioned_but_not_closing ||= @merge_request.issues_mentioned_but_not_closing(current_user)
end
def mr_change_branches_path(merge_request)
new_namespace_project_merge_request_path(
@project.namespace, @project,
merge_request: {
- source_project_id: @merge_request.source_project_id,
- target_project_id: @merge_request.target_project_id,
- source_branch: @merge_request.source_branch,
- target_branch: @merge_request.target_branch,
+ source_project_id: merge_request.source_project_id,
+ target_project_id: merge_request.target_project_id,
+ source_branch: merge_request.source_branch,
+ target_branch: merge_request.target_branch,
},
change_branches: true
)
diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb
index e0b8dc1393b..2e3a15bc1b9 100644
--- a/app/helpers/namespaces_helper.rb
+++ b/app/helpers/namespaces_helper.rb
@@ -1,4 +1,8 @@
module NamespacesHelper
+ def namespace_id_from(params)
+ params.dig(:project, :namespace_id) || params[:namespace_id]
+ end
+
def namespaces_options(selected = :current_user, display_path: false, extra_group: nil)
groups = current_user.owned_groups + current_user.masters_groups
@@ -10,7 +14,7 @@ module NamespacesHelper
data_attr_users = { 'data-options-parent' => 'users' }
group_opts = [
- "Groups", groups.sort_by(&:human_name).map { |g| [display_path ? g.path : g.human_name, g.id, data_attr_group] }
+ "Groups", groups.sort_by(&:human_name).map { |g| [display_path ? g.full_path : g.human_name, g.id, data_attr_group] }
]
users_opts = [
@@ -29,7 +33,7 @@ module NamespacesHelper
end
def namespace_icon(namespace, size = 40)
- if namespace.kind_of?(Group)
+ if namespace.is_a?(Group)
group_icon(namespace)
else
avatar_icon(namespace.owner.email, size)
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index e21178c7377..c1523b4dabf 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -1,10 +1,4 @@
module NavHelper
- def page_sidebar_class
- if pinned_nav?
- "page-sidebar-expanded page-sidebar-pinned"
- end
- end
-
def page_gutter_class
if current_path?('merge_requests#show') ||
current_path?('merge_requests#diffs') ||
@@ -32,10 +26,6 @@ module NavHelper
class_name = ''
class_name << " with-horizontal-nav" if defined?(nav) && nav
- if pinned_nav?
- class_name << " header-sidebar-expanded header-sidebar-pinned"
- end
-
class_name
end
@@ -46,8 +36,4 @@ module NavHelper
def nav_control_class
"nav-control" if current_user
end
-
- def pinned_nav?
- cookies[:pin_nav] == 'true'
- end
end
diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb
index 7d4d049101a..3286a92a8a7 100644
--- a/app/helpers/page_layout_helper.rb
+++ b/app/helpers/page_layout_helper.rb
@@ -34,6 +34,10 @@ module PageLayoutHelper
end
end
+ def favicon
+ Rails.env.development? ? 'favicon-blue.ico' : 'favicon.ico'
+ end
+
def page_image
default = image_url('gitlab_logo.png')
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index 6e68aad4cb7..c3a08d76318 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -23,7 +23,7 @@ module PreferencesHelper
if defined.size != DASHBOARD_CHOICES.size
# Ensure that anyone adding new options updates this method too
- raise RuntimeError, "`User` defines #{defined.size} dashboard choices," +
+ raise "`User` defines #{defined.size} dashboard choices," \
" but `DASHBOARD_CHOICES` defined #{DASHBOARD_CHOICES.size}."
else
defined.map do |key, _|
@@ -41,10 +41,6 @@ module PreferencesHelper
]
end
- def user_application_theme
- Gitlab::Themes.for_user(current_user).css_class
- end
-
def user_color_scheme
Gitlab::ColorSchemes.for_user(current_user).css_class
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index eb98204285d..735a355c25a 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -150,6 +150,15 @@ module ProjectsHelper
).html_safe
end
+ def link_to_autodeploy_doc
+ link_to 'About auto deploy', help_page_path('ci/autodeploy/index'), target: '_blank'
+ end
+
+ def autodeploy_flash_notice(branch_name)
+ "Branch <strong>#{truncate(sanitize(branch_name))}</strong> was created. To set up auto deploy, \
+ choose a GitLab CI Yaml template and commit your changes. #{link_to_autodeploy_doc}".html_safe
+ end
+
private
def repo_children_classes(field)
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index ff787fb4131..8ad3851fb9a 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -30,7 +30,7 @@ module SortingHelper
}
if current_controller?('admin/projects')
- options.merge!(sort_value_largest_repo => sort_title_largest_repo)
+ options[sort_value_largest_repo] = sort_title_largest_repo
end
options
diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb
index b3f50ceebe4..fb95f2b565e 100644
--- a/app/helpers/submodule_helper.rb
+++ b/app/helpers/submodule_helper.rb
@@ -37,8 +37,8 @@ module SubmoduleHelper
end
def self_url?(url, namespace, project)
- return true if url == [ Gitlab.config.gitlab.url, '/', namespace, '/',
- project, '.git' ].join('')
+ return true if url == [Gitlab.config.gitlab.url, '/', namespace, '/',
+ project, '.git'].join('')
url == gitlab_shell.url_to_repo([namespace, '/', project].join(''))
end
@@ -48,8 +48,8 @@ module SubmoduleHelper
end
def standard_links(host, namespace, project, commit)
- base = [ 'https://', host, '/', namespace, '/', project ].join('')
- [base, [ base, '/tree/', commit ].join('')]
+ base = ['https://', host, '/', namespace, '/', project].join('')
+ [base, [base, '/tree/', commit].join('')]
end
def relative_self_links(url, commit)
@@ -63,7 +63,7 @@ module SubmoduleHelper
namespace = components.pop.gsub(/^\.\.$/, '')
if namespace.empty?
- namespace = @project.namespace.path
+ namespace = @project.namespace.full_path
end
[
diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb
index 547f6258909..1a55ee05996 100644
--- a/app/helpers/tab_helper.rb
+++ b/app/helpers/tab_helper.rb
@@ -99,7 +99,7 @@ module TabHelper
return 'active'
end
- if ['services', 'hooks', 'deploy_keys', 'protected_branches'].include? controller.controller_name
+ if %w(services hooks deploy_keys protected_branches).include? controller.controller_name
"active"
end
end
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index c568cca9e5e..7f8efb0a4ac 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -3,6 +3,10 @@ module TodosHelper
@todos_pending_count ||= current_user.todos_pending_count
end
+ def todos_count_format(count)
+ count > 99 ? '99+' : count
+ end
+
def todos_done_count
@todos_done_count ||= current_user.todos_done_count
end
@@ -15,6 +19,7 @@ module TodosHelper
when Todo::MARKED then 'added a todo for'
when Todo::APPROVAL_REQUIRED then 'set you as an approver for'
when Todo::UNMERGEABLE then 'Could not merge'
+ when Todo::DIRECTLY_ADDRESSED then 'directly addressed you on'
end
end
@@ -86,7 +91,10 @@ module TodosHelper
[
{ id: '', text: 'Any Action' },
{ id: Todo::ASSIGNED, text: 'Assigned' },
- { id: Todo::MENTIONED, text: 'Mentioned' }
+ { id: Todo::MENTIONED, text: 'Mentioned' },
+ { id: Todo::MARKED, text: 'Added' },
+ { id: Todo::BUILD_FAILED, text: 'Pipelines' },
+ { id: Todo::DIRECTLY_ADDRESSED, text: 'Directly addressed' }
]
end
@@ -142,6 +150,6 @@ module TodosHelper
private
def show_todo_state?(todo)
- (todo.target.is_a?(MergeRequest) || todo.target.is_a?(Issue)) && ['closed', 'merged'].include?(todo.target.state)
+ (todo.target.is_a?(MergeRequest) || todo.target.is_a?(Issue)) && %w(closed merged).include?(todo.target.state)
end
end
diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb
index fc93acfe63e..169cedeb796 100644
--- a/app/helpers/visibility_level_helper.rb
+++ b/app/helpers/visibility_level_helper.rb
@@ -89,13 +89,9 @@ module VisibilityLevelHelper
current_application_settings.restricted_visibility_levels || []
end
- def default_project_visibility
- current_application_settings.default_project_visibility
- end
-
- def default_group_visibility
- current_application_settings.default_group_visibility
- end
+ delegate :default_project_visibility,
+ :default_group_visibility,
+ to: :current_application_settings
def skip_level?(form_model, level)
form_model.is_a?(Project) && !form_model.visibility_level_allowed?(level)
diff --git a/app/helpers/wiki_helper.rb b/app/helpers/wiki_helper.rb
new file mode 100644
index 00000000000..3e3f6246fc5
--- /dev/null
+++ b/app/helpers/wiki_helper.rb
@@ -0,0 +1,13 @@
+module WikiHelper
+ # Produces a pure text breadcrumb for a given page.
+ #
+ # page_slug - The slug of a WikiPage object.
+ #
+ # Returns a String composed of the capitalized name of each directory and the
+ # capitalized name of the page itself.
+ def breadcrumb(page_slug)
+ page_slug.split('/').
+ map { |dir_or_page| WikiPage.unhyphenize(dir_or_page).capitalize }.
+ join(' / ')
+ end
+end
diff --git a/app/mailers/emails/pipelines.rb b/app/mailers/emails/pipelines.rb
index 9460a6cd2be..f9f45ab987b 100644
--- a/app/mailers/emails/pipelines.rb
+++ b/app/mailers/emails/pipelines.rb
@@ -22,8 +22,8 @@ module Emails
mail(bcc: recipients,
subject: pipeline_subject(status),
skip_premailer: true) do |format|
- format.html { render layout: false }
- format.text
+ format.html { render layout: 'mailer' }
+ format.text { render layout: 'mailer' }
end
end
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 0cd3456b4de..5b9226a6b81 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -151,7 +151,7 @@ class Notify < BaseMailer
headers['In-Reply-To'] = message_id(model)
headers['References'] = message_id(model)
- headers[:subject].prepend('Re: ') if headers[:subject]
+ headers[:subject]&.prepend('Re: ')
mail_thread(model, headers)
end
diff --git a/app/mailers/repository_check_mailer.rb b/app/mailers/repository_check_mailer.rb
index 21db2fe04a0..22a9f5da646 100644
--- a/app/mailers/repository_check_mailer.rb
+++ b/app/mailers/repository_check_mailer.rb
@@ -1,10 +1,11 @@
class RepositoryCheckMailer < BaseMailer
def notify(failed_count)
- if failed_count == 1
- @message = "One project failed its last repository check"
- else
- @message = "#{failed_count} projects failed their last repository check"
- end
+ @message =
+ if failed_count == 1
+ "One project failed its last repository check"
+ else
+ "#{failed_count} projects failed their last repository check"
+ end
mail(
to: User.admins.pluck(:email),
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 9a4557524c4..dc36c754438 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -5,7 +5,7 @@ class ApplicationSetting < ActiveRecord::Base
add_authentication_token_field :runners_registration_token
add_authentication_token_field :health_check_access_token
- CACHE_KEY = 'application_setting.last'
+ CACHE_KEY = 'application_setting.last'.freeze
DOMAIN_LIST_SEPARATOR = %r{\s*[,;]\s* # comma or semicolon, optionally surrounded by whitespace
| # or
\s # any whitespace character
@@ -76,6 +76,12 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
numericality: { only_integer: true, greater_than: 0 }
+ validates :max_artifacts_size,
+ presence: true,
+ numericality: { only_integer: true, greater_than: 0 }
+
+ validates :default_artifacts_expire_in, presence: true, duration: true
+
validates :container_registry_token_expire_delay,
presence: true,
numericality: { only_integer: true, greater_than: 0 }
@@ -116,31 +122,25 @@ class ApplicationSetting < ActiveRecord::Base
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
validates_each :restricted_visibility_levels do |record, attr, value|
- unless value.nil?
- value.each do |level|
- unless Gitlab::VisibilityLevel.options.has_value?(level)
- record.errors.add(attr, "'#{level}' is not a valid visibility level")
- end
+ value&.each do |level|
+ unless Gitlab::VisibilityLevel.options.has_value?(level)
+ record.errors.add(attr, "'#{level}' is not a valid visibility level")
end
end
end
validates_each :import_sources do |record, attr, value|
- unless value.nil?
- value.each do |source|
- unless Gitlab::ImportSources.options.has_value?(source)
- record.errors.add(attr, "'#{source}' is not a import source")
- end
+ value&.each do |source|
+ unless Gitlab::ImportSources.options.has_value?(source)
+ record.errors.add(attr, "'#{source}' is not a import source")
end
end
end
validates_each :disabled_oauth_sign_in_sources do |record, attr, value|
- unless value.nil?
- value.each do |source|
- unless Devise.omniauth_providers.include?(source.to_sym)
- record.errors.add(attr, "'#{source}' is not an OAuth sign-in source")
- end
+ value&.each do |source|
+ unless Devise.omniauth_providers.include?(source.to_sym)
+ record.errors.add(attr, "'#{source}' is not an OAuth sign-in source")
end
end
end
@@ -174,6 +174,7 @@ class ApplicationSetting < ActiveRecord::Base
after_sign_up_text: nil,
akismet_enabled: false,
container_registry_token_expire_delay: 5,
+ default_artifacts_expire_in: '30 days',
default_branch_protection: Settings.gitlab['default_branch_protection'],
default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
default_projects_limit: Settings.gitlab['default_projects_limit'],
@@ -207,9 +208,9 @@ class ApplicationSetting < ActiveRecord::Base
sign_in_text: nil,
signin_enabled: Settings.gitlab['signin_enabled'],
signup_enabled: Settings.gitlab['signup_enabled'],
+ terminal_max_session_time: 0,
two_factor_grace_period: 48,
- user_default_external: false,
- terminal_max_session_time: 0
+ user_default_external: false
}
end
@@ -221,6 +222,14 @@ class ApplicationSetting < ActiveRecord::Base
create(defaults)
end
+ def self.human_attribute_name(attr, _options = {})
+ if attr == :default_artifacts_expire_in
+ 'Default artifacts expiration'
+ else
+ super
+ end
+ end
+
def home_page_url_column_exist
ActiveRecord::Base.connection.column_exists?(:application_settings, :home_page_url)
end
@@ -230,11 +239,11 @@ class ApplicationSetting < ActiveRecord::Base
end
def domain_whitelist_raw
- self.domain_whitelist.join("\n") unless self.domain_whitelist.nil?
+ self.domain_whitelist&.join("\n")
end
def domain_blacklist_raw
- self.domain_blacklist.join("\n") unless self.domain_blacklist.nil?
+ self.domain_blacklist&.join("\n")
end
def domain_whitelist_raw=(values)
diff --git a/app/models/award_emoji.rb b/app/models/award_emoji.rb
index 46b17479d6d..6937ad3bdd9 100644
--- a/app/models/award_emoji.rb
+++ b/app/models/award_emoji.rb
@@ -16,6 +16,14 @@ class AwardEmoji < ActiveRecord::Base
scope :downvotes, -> { where(name: DOWNVOTE_NAME) }
scope :upvotes, -> { where(name: UPVOTE_NAME) }
+ class << self
+ def votes_for_collection(ids, type)
+ select('name', 'awardable_id', 'COUNT(*) as count').
+ where('name IN (?) AND awardable_type = ? AND awardable_id IN (?)', [DOWNVOTE_NAME, UPVOTE_NAME], type, ids).
+ group('name', 'awardable_id')
+ end
+ end
+
def downvote?
self.name == DOWNVOTE_NAME
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 5213ea9d02b..77aba91f65c 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -20,10 +20,12 @@ module Ci
end
serialize :options
- serialize :yaml_variables, Gitlab::Serialize::Ci::Variables
+ serialize :yaml_variables, Gitlab::Serializer::Ci::Variables
+
+ delegate :name, to: :project, prefix: true
validates :coverage, numericality: true, allow_blank: true
- validates_presence_of :ref
+ validates :ref, presence: true
scope :unstarted, ->() { where(runner_id: nil) }
scope :ignore_failures, ->() { where(allow_failure: false) }
@@ -62,33 +64,10 @@ module Ci
new_build.save
end
- def retry(build, user = nil)
- new_build = Ci::Build.create(
- ref: build.ref,
- tag: build.tag,
- options: build.options,
- commands: build.commands,
- tag_list: build.tag_list,
- project: build.project,
- pipeline: build.pipeline,
- name: build.name,
- allow_failure: build.allow_failure,
- stage: build.stage,
- stage_idx: build.stage_idx,
- trigger_request: build.trigger_request,
- yaml_variables: build.yaml_variables,
- when: build.when,
- user: user,
- environment: build.environment,
- status_event: 'enqueue'
- )
-
- MergeRequests::AddTodoWhenBuildFailsService
- .new(build.project, nil)
- .close(new_build)
-
- build.pipeline.mark_as_processable_after_stage(build.stage_idx)
- new_build
+ def retry(build, current_user)
+ Ci::RetryBuildService
+ .new(build.project, current_user)
+ .execute(build)
end
end
@@ -136,7 +115,7 @@ module Ci
project.builds_enabled? && commands.present? && manual? && skipped?
end
- def play(current_user = nil)
+ def play(current_user)
# Try to queue a current build
if self.enqueue
self.update(user: current_user)
@@ -256,10 +235,6 @@ module Ci
gl_project_id
end
- def project_name
- project.name
- end
-
def repo_url
auth = "gitlab-ci-token:#{ensure_token!}@"
project.http_url_to_repo.sub(/^https?:\/\//) do |prefix|
@@ -280,7 +255,7 @@ module Ci
return unless regex
matches = text.scan(Regexp.new(regex)).last
- matches = matches.last if matches.kind_of?(Array)
+ matches = matches.last if matches.is_a?(Array)
coverage = matches.gsub(/\d+(\.\d+)?/).first
if coverage.present?
@@ -509,7 +484,7 @@ module Ci
def artifacts_expire_in=(value)
self.artifacts_expire_at =
if value
- Time.now + ChronicDuration.parse(value)
+ ChronicDuration.parse(value)&.seconds&.from_now
end
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index bbc358adb83..80e11a5b58f 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -14,9 +14,11 @@ module Ci
has_many :builds, foreign_key: :commit_id
has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id
- validates_presence_of :sha, unless: :importing?
- validates_presence_of :ref, unless: :importing?
- validates_presence_of :status, unless: :importing?
+ delegate :id, to: :project, prefix: true
+
+ validates :sha, presence: { unless: :importing? }
+ validates :ref, presence: { unless: :importing? }
+ validates :status, presence: { unless: :importing? }
validate :valid_commit_sha, unless: :importing?
after_create :keep_around_commits, unless: :importing?
@@ -93,8 +95,11 @@ module Ci
.select("max(#{quoted_table_name}.id)")
.group(:ref, :sha)
- relation = ref ? where(ref: ref) : self
- relation.where(id: max_id)
+ if ref
+ where(ref: ref, id: max_id.where(ref: ref))
+ else
+ where(id: max_id)
+ end
end
def self.latest_status(ref = nil)
@@ -150,10 +155,6 @@ module Ci
builds.latest.with_artifacts_not_expired.includes(project: [:namespace])
end
- def project_id
- project.id
- end
-
# For now the only user who participates is the user who triggered
def participants(_current_user = nil)
Array(user)
@@ -214,21 +215,17 @@ module Ci
def cancel_running
Gitlab::OptimisticLocking.retry_lock(
statuses.cancelable) do |cancelable|
- cancelable.each(&:cancel)
+ cancelable.find_each(&:cancel)
end
end
- def retry_failed(user)
- Gitlab::OptimisticLocking.retry_lock(
- builds.latest.failed_or_canceled) do |failed_or_canceled|
- failed_or_canceled.select(&:retryable?).each do |build|
- Ci::Build.retry(build, user)
- end
- end
+ def retry_failed(current_user)
+ Ci::RetryPipelineService.new(project, current_user)
+ .execute(self)
end
def mark_as_processable_after_stage(stage_idx)
- builds.skipped.where('stage_idx > ?', stage_idx).find_each(&:process)
+ builds.skipped.after_stage(stage_idx).find_each(&:process)
end
def latest?
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index ed1843ba005..4863c34a6a6 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -4,8 +4,8 @@ module Ci
RUNNER_QUEUE_EXPIRY_TIME = 60.minutes
LAST_CONTACT_TIME = 1.hour.ago
- AVAILABLE_SCOPES = %w[specific shared active paused online]
- FORM_EDITABLE = %i[description tag_list active run_untagged locked]
+ AVAILABLE_SCOPES = %w[specific shared active paused online].freeze
+ FORM_EDITABLE = %i[description tag_list active run_untagged locked].freeze
has_many :builds
has_many :runner_projects, dependent: :destroy
@@ -22,8 +22,6 @@ module Ci
scope :online, ->() { where('contacted_at > ?', LAST_CONTACT_TIME) }
scope :ordered, ->() { order(id: :desc) }
- after_save :tick_runner_queue, if: :form_editable_changed?
-
scope :owned_or_shared, ->(project_id) do
joins('LEFT JOIN ci_runner_projects ON ci_runner_projects.runner_id = ci_runners.id')
.where("ci_runner_projects.gl_project_id = :project_id OR ci_runners.is_shared = true", project_id: project_id)
@@ -40,6 +38,8 @@ module Ci
acts_as_taggable
+ after_destroy :cleanup_runner_queue
+
# Searches for runners matching the given query.
#
# This method uses ILIKE on PostgreSQL and LIKE on MySQL.
@@ -147,14 +147,14 @@ module Ci
private
- def runner_queue_key
- "runner:build_queue:#{self.token}"
+ def cleanup_runner_queue
+ Gitlab::Redis.with do |redis|
+ redis.del(runner_queue_key)
+ end
end
- def form_editable_changed?
- FORM_EDITABLE.any? do |editable|
- public_send("#{editable}_changed?")
- end
+ def runner_queue_key
+ "runner:build_queue:#{self.token}"
end
def tag_constraints
diff --git a/app/models/ci/runner_project.rb b/app/models/ci/runner_project.rb
index 1f9baeca5b1..234376a7e4c 100644
--- a/app/models/ci/runner_project.rb
+++ b/app/models/ci/runner_project.rb
@@ -5,6 +5,6 @@ module Ci
belongs_to :runner
belongs_to :project, foreign_key: :gl_project_id
- validates_uniqueness_of :runner_id, scope: :gl_project_id
+ validates :runner_id, uniqueness: { scope: :gl_project_id }
end
end
diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb
index 62889fe80d8..39a1dd86241 100644
--- a/app/models/ci/trigger.rb
+++ b/app/models/ci/trigger.rb
@@ -7,8 +7,8 @@ module Ci
belongs_to :project, foreign_key: :gl_project_id
has_many :trigger_requests, dependent: :destroy
- validates_presence_of :token
- validates_uniqueness_of :token
+ validates :token, presence: true
+ validates :token, uniqueness: true
before_validation :set_default_values
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 46f06733da1..0a18986ef26 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -22,12 +22,12 @@ class Commit
DIFF_HARD_LIMIT_LINES = 50000
# The SHA can be between 7 and 40 hex characters.
- COMMIT_SHA_PATTERN = '\h{7,40}'
+ COMMIT_SHA_PATTERN = '\h{7,40}'.freeze
class << self
def decorate(commits, project)
commits.map do |commit|
- if commit.kind_of?(Commit)
+ if commit.is_a?(Commit)
commit
else
self.new(commit, project)
@@ -105,7 +105,7 @@ class Commit
end
def diff_line_count
- @diff_line_count ||= Commit::diff_line_count(raw_diffs)
+ @diff_line_count ||= Commit.diff_line_count(raw_diffs)
@diff_line_count
end
@@ -122,11 +122,12 @@ class Commit
def full_title
return @full_title if @full_title
- if safe_message.blank?
- @full_title = no_commit_message
- else
- @full_title = safe_message.split("\n", 2).first
- end
+ @full_title =
+ if safe_message.blank?
+ no_commit_message
+ else
+ safe_message.split("\n", 2).first
+ end
end
# Returns the commits description
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 9547c57b2ae..fc750a3e5e9 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -10,10 +10,11 @@ class CommitStatus < ActiveRecord::Base
belongs_to :user
delegate :commit, to: :pipeline
+ delegate :sha, :short_sha, to: :pipeline
validates :pipeline, presence: true, unless: :importing?
- validates_presence_of :name
+ validates :name, presence: true
alias_attribute :author, :user
@@ -23,9 +24,6 @@ class CommitStatus < ActiveRecord::Base
where(id: max_id.group(:name, :commit_id))
end
- scope :retried, -> { where.not(id: latest) }
- scope :ordered, -> { order(:name) }
-
scope :failed_but_allowed, -> do
where(allow_failure: true, status: [:failed, :canceled])
end
@@ -36,8 +34,11 @@ class CommitStatus < ActiveRecord::Base
false, all_state_names - [:failed, :canceled])
end
+ scope :retried, -> { where.not(id: latest) }
+ scope :ordered, -> { order(:name) }
scope :latest_ordered, -> { latest.ordered.includes(project: :namespace) }
scope :retried_ordered, -> { retried.ordered.includes(project: :namespace) }
+ scope :after_stage, -> (index) { where('stage_idx > ?', index) }
state_machine :status do
event :enqueue do
@@ -102,8 +103,6 @@ class CommitStatus < ActiveRecord::Base
end
end
- delegate :sha, :short_sha, to: :pipeline
-
def before_sha
pipeline.before_sha || Gitlab::Git::BLANK_SHA
end
diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb
index a600f9c14c5..8ea95beed79 100644
--- a/app/models/concerns/cache_markdown_field.rb
+++ b/app/models/concerns/cache_markdown_field.rb
@@ -11,14 +11,15 @@ module CacheMarkdownField
# Knows about the relationship between markdown and html field names, and
# stores the rendering contexts for the latter
class FieldData
- extend Forwardable
-
def initialize
@data = {}
end
- def_delegators :@data, :[], :[]=
- def_delegator :@data, :keys, :markdown_fields
+ delegate :[], :[]=, to: :@data
+
+ def markdown_fields
+ @data.keys
+ end
def html_field(markdown_field)
"#{markdown_field}_html"
@@ -45,7 +46,7 @@ module CacheMarkdownField
Project
Release
Snippet
- ]
+ ].freeze
def self.caching_classes
CACHING_CLASSES.map(&:constantize)
diff --git a/app/models/concerns/case_sensitivity.rb b/app/models/concerns/case_sensitivity.rb
index fe0cea8465f..034e9f40ff0 100644
--- a/app/models/concerns/case_sensitivity.rb
+++ b/app/models/concerns/case_sensitivity.rb
@@ -13,11 +13,12 @@ module CaseSensitivity
params.each do |key, value|
column = ActiveRecord::Base.connection.quote_table_name(key)
- if cast_lower
- condition = "LOWER(#{column}) = LOWER(:value)"
- else
- condition = "#{column} = :value"
- end
+ condition =
+ if cast_lower
+ "LOWER(#{column}) = LOWER(:value)"
+ else
+ "#{column} = :value"
+ end
criteria = criteria.where(condition, value: value)
end
diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb
index 431c0354969..aea359e70bb 100644
--- a/app/models/concerns/has_status.rb
+++ b/app/models/concerns/has_status.rb
@@ -1,12 +1,12 @@
module HasStatus
extend ActiveSupport::Concern
- DEFAULT_STATUS = 'created'
- AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped]
- STARTED_STATUSES = %w[running success failed skipped]
- ACTIVE_STATUSES = %w[pending running]
- COMPLETED_STATUSES = %w[success failed canceled skipped]
- ORDERED_STATUSES = %w[failed pending running canceled success skipped]
+ DEFAULT_STATUS = 'created'.freeze
+ AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped].freeze
+ STARTED_STATUSES = %w[running success failed skipped].freeze
+ ACTIVE_STATUSES = %w[pending running].freeze
+ COMPLETED_STATUSES = %w[success failed canceled skipped].freeze
+ ORDERED_STATUSES = %w[failed pending running canceled success skipped].freeze
class_methods do
def status_sql
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 3517969eabc..37c727b5d9f 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -15,6 +15,11 @@ module Issuable
include Taskable
include TimeTrackable
+ # This object is used to gather issuable meta data for displaying
+ # upvotes, downvotes, notes and closing merge requests count for issues and merge requests
+ # lists avoiding n+1 queries and improving performance.
+ IssuableMeta = Struct.new(:upvotes, :downvotes, :notes_count, :merge_requests_count)
+
included do
cache_markdown_field :title, pipeline: :single_line
cache_markdown_field :description
@@ -41,6 +46,17 @@ module Issuable
has_one :metrics
+ delegate :name,
+ :email,
+ to: :author,
+ prefix: true
+
+ delegate :name,
+ :email,
+ to: :assignee,
+ allow_nil: true,
+ prefix: true
+
validates :author, presence: true
validates :title, presence: true, length: { maximum: 255 }
@@ -63,21 +79,10 @@ module Issuable
scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) }
scope :join_project, -> { joins(:project) }
- scope :inc_notes_with_associations, -> { includes(notes: [ :project, :author, :award_emoji ]) }
+ scope :inc_notes_with_associations, -> { includes(notes: [:project, :author, :award_emoji]) }
scope :references_project, -> { references(:project) }
scope :non_archived, -> { join_project.where(projects: { archived: false }) }
- delegate :name,
- :email,
- to: :author,
- prefix: true
-
- delegate :name,
- :email,
- to: :assignee,
- allow_nil: true,
- prefix: true
-
attr_mentionable :title, pipeline: :single_line
attr_mentionable :description
@@ -95,8 +100,8 @@ module Issuable
def update_assignee_cache_counts
# make sure we flush the cache for both the old *and* new assignees(if they exist)
previous_assignee = User.find_by_id(assignee_id_was) if assignee_id_was
- previous_assignee.update_cache_counts if previous_assignee
- assignee.update_cache_counts if assignee
+ previous_assignee&.update_cache_counts
+ assignee&.update_cache_counts
end
# We want to use optimistic lock for cases when only title or description are involved
@@ -177,7 +182,7 @@ module Issuable
def grouping_columns(sort)
grouping_columns = [arel_table[:id]]
- if ["milestone_due_desc", "milestone_due_asc"].include?(sort)
+ if %w(milestone_due_desc milestone_due_asc).include?(sort)
milestone_table = Milestone.arel_table
grouping_columns << milestone_table[:id]
grouping_columns << milestone_table[:due_date]
@@ -230,7 +235,7 @@ module Issuable
# DEPRECATED
repository: project.hook_attrs.slice(:name, :url, :description, :homepage)
}
- hook_data.merge!(assignee: assignee.hook_attrs) if assignee
+ hook_data[:assignee] = assignee.hook_attrs if assignee
hook_data
end
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index ef2c1e5d414..7e56e371b27 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -44,8 +44,15 @@ module Mentionable
end
def all_references(current_user = nil, extractor: nil)
- extractor ||= Gitlab::ReferenceExtractor.
- new(project, current_user)
+ # Use custom extractor if it's passed in the function parameters.
+ if extractor
+ @extractor = extractor
+ else
+ @extractor ||= Gitlab::ReferenceExtractor.
+ new(project, current_user)
+
+ @extractor.reset_memoized_values
+ end
self.class.mentionable_attrs.each do |attr, options|
text = __send__(attr)
@@ -55,16 +62,20 @@ module Mentionable
skip_project_check: skip_project_check?
)
- extractor.analyze(text, options)
+ @extractor.analyze(text, options)
end
- extractor
+ @extractor
end
def mentioned_users(current_user = nil)
all_references(current_user).users
end
+ def directly_addressed_users(current_user = nil)
+ all_references(current_user).directly_addressed_users
+ end
+
# Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference.
def referenced_mentionables(current_user = self.author)
refs = all_references(current_user)
diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb
index e9450dd0c26..f449229864d 100644
--- a/app/models/concerns/milestoneish.rb
+++ b/app/models/concerns/milestoneish.rb
@@ -73,7 +73,7 @@ module Milestoneish
def memoize_per_user(user, method_name)
@memoized ||= {}
@memoized[method_name] ||= {}
- @memoized[method_name][user.try!(:id)] ||= yield
+ @memoized[method_name][user&.id] ||= yield
end
# override in a class that includes this module to get a faster query
diff --git a/app/models/concerns/reactive_service.rb b/app/models/concerns/reactive_service.rb
index e1f868a299b..713246039c1 100644
--- a/app/models/concerns/reactive_service.rb
+++ b/app/models/concerns/reactive_service.rb
@@ -5,6 +5,6 @@ module ReactiveService
include ReactiveCaching
# Default cache key: class name + project_id
- self.reactive_cache_key = ->(service) { [ service.class.model_name.singular, service.project_id ] }
+ self.reactive_cache_key = ->(service) { [service.class.model_name.singular, service.project_id] }
end
end
diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb
index 2b93aa30c0f..9f6d215ceb3 100644
--- a/app/models/concerns/routable.rb
+++ b/app/models/concerns/routable.rb
@@ -1,5 +1,5 @@
# Store object full path in separate table for easy lookup and uniq validation
-# Object must have path db field and respond to full_path and full_path_changed? methods.
+# Object must have name and path db fields and respond to parent and parent_changed? methods.
module Routable
extend ActiveSupport::Concern
@@ -9,7 +9,13 @@ module Routable
validates_associated :route
validates :route, presence: true
- before_validation :update_route_path, if: :full_path_changed?
+ scope :with_route, -> { includes(:route) }
+
+ before_validation do
+ if full_path_changed? || full_name_changed?
+ prepare_route
+ end
+ end
end
class_methods do
@@ -77,10 +83,62 @@ module Routable
end
end
+ def full_name
+ if route && route.name.present?
+ @full_name ||= route.name
+ else
+ update_route if persisted?
+
+ build_full_name
+ end
+ end
+
+ def full_path
+ if route && route.path.present?
+ @full_path ||= route.path
+ else
+ update_route if persisted?
+
+ build_full_path
+ end
+ end
+
private
- def update_route_path
+ def full_name_changed?
+ name_changed? || parent_changed?
+ end
+
+ def full_path_changed?
+ path_changed? || parent_changed?
+ end
+
+ def build_full_name
+ if parent && name
+ parent.human_name + ' / ' + name
+ else
+ name
+ end
+ end
+
+ def build_full_path
+ if parent && path
+ parent.full_path + '/' + path
+ else
+ path
+ end
+ end
+
+ def update_route
+ prepare_route
+ route.save
+ end
+
+ def prepare_route
route || build_route(source: self)
- route.path = full_path
+ route.path = build_full_path
+ route.name = build_full_name
+ @full_path = nil
+ @full_name = nil
end
end
diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb
index 7edb0acd56c..b9a2d812edd 100644
--- a/app/models/concerns/sortable.rb
+++ b/app/models/concerns/sortable.rb
@@ -46,11 +46,12 @@ module Sortable
where("label_links.target_id = #{target_column}").
reorder(nil)
- if target_type_column
- query = query.where("label_links.target_type = #{target_type_column}")
- else
- query = query.where(label_links: { target_type: target_type })
- end
+ query =
+ if target_type_column
+ query.where("label_links.target_type = #{target_type_column}")
+ else
+ query.where(label_links: { target_type: target_type })
+ end
query = query.where.not(title: excluded_labels) if excluded_labels.present?
diff --git a/app/models/concerns/spammable.rb b/app/models/concerns/spammable.rb
index 423ae98a60e..107e6764ba2 100644
--- a/app/models/concerns/spammable.rb
+++ b/app/models/concerns/spammable.rb
@@ -13,7 +13,7 @@ module Spammable
attr_accessor :spam
attr_accessor :spam_log
- after_validation :check_for_spam, on: :create
+ after_validation :check_for_spam, on: [:create, :update]
cattr_accessor :spammable_attrs, instance_accessor: false do
[]
@@ -22,6 +22,10 @@ module Spammable
delegate :ip_address, :user_agent, to: :user_agent_detail, allow_nil: true
end
+ def submittable_as_spam_by?(current_user)
+ current_user && current_user.admin? && submittable_as_spam?
+ end
+
def submittable_as_spam?
if user_agent_detail
user_agent_detail.submittable? && current_application_settings.akismet_enabled
diff --git a/app/models/concerns/uniquify.rb b/app/models/concerns/uniquify.rb
new file mode 100644
index 00000000000..a7fe5951b6e
--- /dev/null
+++ b/app/models/concerns/uniquify.rb
@@ -0,0 +1,30 @@
+class Uniquify
+ # Return a version of the given 'base' string that is unique
+ # by appending a counter to it. Uniqueness is determined by
+ # repeated calls to the passed block.
+ #
+ # If `base` is a function/proc, we expect that calling it with a
+ # candidate counter returns a string to test/return.
+ def string(base)
+ @base = base
+ @counter = nil
+
+ increment_counter! while yield(base_string)
+ base_string
+ end
+
+ private
+
+ def base_string
+ if @base.respond_to?(:call)
+ @base.call(@counter)
+ else
+ "#{@base}#{@counter}"
+ end
+ end
+
+ def increment_counter!
+ @counter ||= 0
+ @counter += 1
+ end
+end
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index 559b3075905..895a91139c9 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -8,7 +8,7 @@ class DiffNote < Note
validates :position, presence: true
validates :diff_line, presence: true
validates :line_code, presence: true, line_code: true
- validates :noteable_type, inclusion: { in: ['Commit', 'MergeRequest'] }
+ validates :noteable_type, inclusion: { in: %w(Commit MergeRequest) }
validates :resolved_by, presence: true, if: :resolved?
validate :positions_complete
validate :verify_supported
diff --git a/app/models/directly_addressed_user.rb b/app/models/directly_addressed_user.rb
new file mode 100644
index 00000000000..0d519c6ac22
--- /dev/null
+++ b/app/models/directly_addressed_user.rb
@@ -0,0 +1,7 @@
+class DirectlyAddressedUser
+ class << self
+ def reference_pattern
+ User.reference_pattern
+ end
+ end
+end
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 803060b3979..1a21b5e52b5 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -126,7 +126,7 @@ class Environment < ActiveRecord::Base
return unless available?
stop!
- stop_action.play(current_user) if stop_action
+ stop_action&.play(current_user)
end
def actions_for(environment)
diff --git a/app/models/event.rb b/app/models/event.rb
index 2662f170765..4b8eac9accf 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -36,18 +36,19 @@ class Event < ActiveRecord::Base
scope :code_push, -> { where(action: PUSHED) }
scope :in_projects, ->(projects) do
- where(project_id: projects.map(&:id)).recent
+ where(project_id: projects).recent
end
- scope :with_associations, -> { includes(project: :namespace) }
+ scope :with_associations, -> { includes(:author, :project, project: :namespace).preload(:target) }
scope :for_milestone_id, ->(milestone_id) { where(target_type: "Milestone", target_id: milestone_id) }
class << self
# Update Gitlab::ContributionsCalendar#activity_dates if this changes
def contributions
- where("action = ? OR (target_type in (?) AND action in (?))",
- Event::PUSHED, ["MergeRequest", "Issue"],
- [Event::CREATED, Event::CLOSED, Event::MERGED])
+ where("action = ? OR (target_type IN (?) AND action IN (?)) OR (target_type = ? AND action = ?)",
+ Event::PUSHED,
+ %w(MergeRequest Issue), [Event::CREATED, Event::CLOSED, Event::MERGED],
+ "Note", Event::COMMENTED)
end
def limit_recent(limit = 20, offset = nil)
diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb
index 26712c19b5a..b973bbcd8da 100644
--- a/app/models/external_issue.rb
+++ b/app/models/external_issue.rb
@@ -43,7 +43,7 @@ class ExternalIssue
end
def reference_link_text(from_project = nil)
- return "##{id}" if /^\d+$/.match(id)
+ return "##{id}" if id =~ /^\d+$/
id
end
diff --git a/app/models/group.rb b/app/models/group.rb
index a5b92283daa..240a17f1dc1 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -81,7 +81,7 @@ class Group < Namespace
end
def to_reference(_from_project = nil, full: nil)
- "#{self.class.reference_prefix}#{name}"
+ "#{self.class.reference_prefix}#{full_path}"
end
def web_url
@@ -206,7 +206,7 @@ class Group < Namespace
end
def members_with_parents
- GroupMember.where(requested_at: nil, source_id: ancestors.map(&:id).push(id))
+ GroupMember.non_request.where(source_id: ancestors.map(&:id).push(id))
end
def users_with_parents
diff --git a/app/models/group_milestone.rb b/app/models/group_milestone.rb
index 7b6db2634b7..86d38e5468b 100644
--- a/app/models/group_milestone.rb
+++ b/app/models/group_milestone.rb
@@ -9,7 +9,7 @@ class GroupMilestone < GlobalMilestone
def self.build(group, projects, title)
super(projects, title).tap do |milestone|
- milestone.group = group if milestone
+ milestone&.group = group
end
end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index d8826b65fcc..de90f19f854 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -15,8 +15,6 @@ class Issue < ActiveRecord::Base
DueThisWeek = DueDateStruct.new('Due This Week', 'week').freeze
DueThisMonth = DueDateStruct.new('Due This Month', 'month').freeze
- ActsAsTaggableOn.strict_case_match = true
-
belongs_to :project
belongs_to :moved_to, class_name: 'Issue'
diff --git a/app/models/label.rb b/app/models/label.rb
index 5b6b9a7a736..f68a8c9cff2 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -11,7 +11,7 @@ class Label < ActiveRecord::Base
cache_markdown_field :description, pipeline: :single_line
- DEFAULT_COLOR = '#428BCA'
+ DEFAULT_COLOR = '#428BCA'.freeze
default_value_for :color, DEFAULT_COLOR
diff --git a/app/models/member.rb b/app/models/member.rb
index 26a6054e00d..0545bd4eedf 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -10,6 +10,8 @@ class Member < ActiveRecord::Base
belongs_to :user
belongs_to :source, polymorphic: true
+ delegate :name, :username, :email, to: :user, prefix: true
+
validates :user, presence: true, unless: :invite?
validates :source, presence: true
validates :user_id, uniqueness: { scope: [:source_type, :source_id],
@@ -47,6 +49,7 @@ class Member < ActiveRecord::Base
scope :invite, -> { where.not(invite_token: nil) }
scope :non_invite, -> { where(invite_token: nil) }
scope :request, -> { where.not(requested_at: nil) }
+ scope :non_request, -> { where(requested_at: nil) }
scope :has_access, -> { active.where('access_level > 0') }
@@ -72,8 +75,6 @@ class Member < ActiveRecord::Base
after_destroy :post_destroy_hook, unless: :pending?
after_commit :refresh_member_authorized_projects
- delegate :name, :username, :email, to: :user, prefix: true
-
default_value_for :notification_level, NotificationSetting.levels[:global]
class << self
diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb
index 204f34f0269..446f9f8f8a7 100644
--- a/app/models/members/group_member.rb
+++ b/app/models/members/group_member.rb
@@ -1,11 +1,11 @@
class GroupMember < Member
- SOURCE_TYPE = 'Namespace'
+ SOURCE_TYPE = 'Namespace'.freeze
belongs_to :group, foreign_key: 'source_id'
# Make sure group member points only to group as it source
default_value_for :source_type, SOURCE_TYPE
- validates_format_of :source_type, with: /\ANamespace\z/
+ validates :source_type, format: { with: /\ANamespace\z/ }
default_scope { where(source_type: SOURCE_TYPE) }
def self.access_level_roles
diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb
index 008fff0857c..912820b51ac 100644
--- a/app/models/members/project_member.rb
+++ b/app/models/members/project_member.rb
@@ -1,5 +1,5 @@
class ProjectMember < Member
- SOURCE_TYPE = 'Project'
+ SOURCE_TYPE = 'Project'.freeze
include Gitlab::ShellAdapter
@@ -7,7 +7,7 @@ class ProjectMember < Member
# Make sure project member points only to project as it source
default_value_for :source_type, SOURCE_TYPE
- validates_format_of :source_type, with: /\AProject\z/
+ validates :source_type, format: { with: /\AProject\z/ }
validates :access_level, inclusion: { in: Gitlab::Access.values }
default_scope { where(source_type: SOURCE_TYPE) }
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 43085f69105..7eb875f1ef5 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -203,7 +203,11 @@ class MergeRequest < ActiveRecord::Base
end
def diff_size
- opts = diff_options || {}
+ # The `#diffs` method ends up at an instance of a class inheriting from
+ # `Gitlab::Diff::FileCollection::Base`, so use those options as defaults
+ # here too, to get the same diff size without performing highlighting.
+ #
+ opts = Gitlab::Diff::FileCollection::Base.default_options.merge(diff_options || {})
raw_diffs(opts).size
end
@@ -527,7 +531,7 @@ class MergeRequest < ActiveRecord::Base
}
if diff_head_commit
- attrs.merge!(last_commit: diff_head_commit.hook_attrs)
+ attrs[:last_commit] = diff_head_commit.hook_attrs
end
attributes.merge!(attrs)
@@ -546,7 +550,7 @@ class MergeRequest < ActiveRecord::Base
# Calculating this information for a number of merge requests requires
# running `ReferenceExtractor` on each of them separately.
# This optimization does not apply to issues from external sources.
- def cache_merge_request_closes_issues!(current_user = self.author)
+ def cache_merge_request_closes_issues!(current_user)
return if project.has_external_issue_tracker?
transaction do
@@ -558,14 +562,10 @@ class MergeRequest < ActiveRecord::Base
end
end
- def closes_issue?(issue)
- closes_issues.include?(issue)
- end
-
# Return the set of issues that will be closed if this merge request is accepted.
def closes_issues(current_user = self.author)
if target_branch == project.default_branch
- messages = [description]
+ messages = [title, description]
messages.concat(commits.map(&:safe_message)) if merge_request_diff
Gitlab::ClosingIssueExtractor.new(project, current_user).
@@ -575,13 +575,13 @@ class MergeRequest < ActiveRecord::Base
end
end
- def issues_mentioned_but_not_closing(current_user = self.author)
+ def issues_mentioned_but_not_closing(current_user)
return [] unless target_branch == project.default_branch
ext = Gitlab::ReferenceExtractor.new(project, current_user)
- ext.analyze(description)
+ ext.analyze("#{title}\n#{description}")
- ext.issues - closes_issues
+ ext.issues - closes_issues(current_user)
end
def target_project_path
@@ -602,7 +602,7 @@ class MergeRequest < ActiveRecord::Base
def source_project_namespace
if source_project && source_project.namespace
- source_project.namespace.path
+ source_project.namespace.full_path
else
"(removed)"
end
@@ -610,7 +610,7 @@ class MergeRequest < ActiveRecord::Base
def target_project_namespace
if target_project && target_project.namespace
- target_project.namespace.path
+ target_project.namespace.full_path
else
"(removed)"
end
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 70bad2a4396..baee00b8fcd 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -7,7 +7,7 @@ class MergeRequestDiff < ActiveRecord::Base
COMMITS_SAFE_SIZE = 100
# Valid types of serialized diffs allowed by Gitlab::Git::Diff
- VALID_CLASSES = [Hash, Rugged::Patch, Rugged::Diff::Delta]
+ VALID_CLASSES = [Hash, Rugged::Patch, Rugged::Diff::Delta].freeze
belongs_to :merge_request
diff --git a/app/models/merge_requests_closing_issues.rb b/app/models/merge_requests_closing_issues.rb
index ab597c37947..daafb137be4 100644
--- a/app/models/merge_requests_closing_issues.rb
+++ b/app/models/merge_requests_closing_issues.rb
@@ -4,4 +4,12 @@ class MergeRequestsClosingIssues < ActiveRecord::Base
validates :merge_request_id, uniqueness: { scope: :issue_id }, presence: true
validates :issue_id, presence: true
+
+ class << self
+ def count_for_collection(ids)
+ group(:issue_id).
+ where(issue_id: ids).
+ pluck('issue_id', 'COUNT(*) as count')
+ end
+ end
end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index c5713fb7818..3137dd32f93 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -7,6 +7,11 @@ class Namespace < ActiveRecord::Base
include Gitlab::CurrentSettings
include Routable
+ # Prevent users from creating unreasonably deep level of nesting.
+ # The number 20 was taken based on maximum nesting level of
+ # Android repo (15) + some extra backup.
+ NUMBER_OF_ANCESTORS_ALLOWED = 20
+
cache_markdown_field :description, pipeline: :description
has_many :projects, dependent: :destroy
@@ -29,13 +34,15 @@ class Namespace < ActiveRecord::Base
length: { maximum: 255 },
namespace: true
+ validate :nesting_level_allowed
+
delegate :name, to: :owner, allow_nil: true, prefix: true
after_update :move_dir, if: :path_changed?
after_commit :refresh_access_of_projects_invited_groups, on: :update, if: -> { previous_changes.key?('share_with_group_lock') }
# Save the storage paths before the projects are destroyed to use them on after destroy
- before_destroy(prepend: true) { @old_repository_storage_paths = repository_storage_paths }
+ before_destroy(prepend: true) { prepare_for_destroy }
after_destroy :rm_dir
scope :root, -> { where('type IS NULL') }
@@ -91,14 +98,8 @@ class Namespace < ActiveRecord::Base
# Work around that by setting their username to "blank", followed by a counter.
path = "blank" if path.blank?
- counter = 0
- base = path
- while Namespace.find_by_path_or_name(path)
- counter += 1
- path = "#{base}#{counter}"
- end
-
- path
+ uniquify = Uniquify.new
+ uniquify.string(path) { |s| Namespace.find_by_path_or_name(s) }
end
end
@@ -170,31 +171,14 @@ class Namespace < ActiveRecord::Base
Gitlab.config.lfs.enabled
end
- def full_path
- if parent
- parent.full_path + '/' + path
- else
- path
- end
- end
-
def shared_runners_enabled?
projects.with_shared_runners.any?
end
- def full_name
- @full_name ||=
- if parent
- parent.full_name + ' / ' + name
- else
- name
- end
- end
-
# Scopes the model on ancestors of the record
def ancestors
if parent_id
- path = route.path
+ path = route ? route.path : full_path
paths = []
until path.blank?
@@ -217,6 +201,18 @@ class Namespace < ActiveRecord::Base
[owner_id]
end
+ def parent_changed?
+ parent_id_changed?
+ end
+
+ def prepare_for_destroy
+ old_repository_storage_paths
+ end
+
+ def old_repository_storage_paths
+ @old_repository_storage_paths ||= repository_storage_paths
+ end
+
private
def repository_storage_paths
@@ -230,7 +226,7 @@ class Namespace < ActiveRecord::Base
def rm_dir
# Remove the namespace directory in all storages paths used by member projects
- @old_repository_storage_paths.each do |repository_storage_path|
+ old_repository_storage_paths.each do |repository_storage_path|
# Move namespace directory into trash.
# We will remove it later async
new_path = "#{path}+#{id}+deleted"
@@ -255,10 +251,6 @@ class Namespace < ActiveRecord::Base
find_each(&:refresh_members_authorized_projects)
end
- def full_path_changed?
- path_changed? || parent_id_changed?
- end
-
def remove_exports!
Gitlab::Popen.popen(%W(find #{export_path} -not -path #{export_path} -delete))
end
@@ -274,4 +266,10 @@ class Namespace < ActiveRecord::Base
path_was
end
end
+
+ def nesting_level_allowed
+ if ancestors.count > Group::NUMBER_OF_ANCESTORS_ALLOWED
+ errors.add(:parent_id, "has too deep level of nesting")
+ end
+ end
end
diff --git a/app/models/network/graph.rb b/app/models/network/graph.rb
index b524ca50ee8..0bbc9451ffd 100644
--- a/app/models/network/graph.rb
+++ b/app/models/network/graph.rb
@@ -188,11 +188,12 @@ module Network
end
# and mark it as reserved
- if parent_time.nil?
- min_time = leaves.first.time
- else
- min_time = parent_time + 1
- end
+ min_time =
+ if parent_time.nil?
+ leaves.first.time
+ else
+ parent_time + 1
+ end
max_time = leaves.last.time
leaves.last.parents(@map).each do |parent|
diff --git a/app/models/note.rb b/app/models/note.rb
index bf090a0438c..d6d5396afa5 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -72,7 +72,7 @@ class Note < ActiveRecord::Base
scope :inc_author, ->{ includes(:author) }
scope :inc_relations_for_view, ->{ includes(:project, :author, :updated_by, :resolved_by, :award_emoji) }
- scope :diff_notes, ->{ where(type: ['LegacyDiffNote', 'DiffNote']) }
+ scope :diff_notes, ->{ where(type: %w(LegacyDiffNote DiffNote)) }
scope :non_diff_notes, ->{ where(type: ['Note', nil]) }
scope :with_associations, -> do
@@ -108,6 +108,12 @@ class Note < ActiveRecord::Base
Discussion.for_diff_notes(active_notes).
map { |d| [d.line_code, d] }.to_h
end
+
+ def count_for_collection(ids, type)
+ user.select('noteable_id', 'COUNT(*) as count').
+ group(:noteable_id).
+ where(noteable_type: type, noteable_id: ids)
+ end
end
def cross_reference?
diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb
index 58f6214bea7..52577bd52ea 100644
--- a/app/models/notification_setting.rb
+++ b/app/models/notification_setting.rb
@@ -35,11 +35,11 @@ class NotificationSetting < ActiveRecord::Base
:merge_merge_request,
:failed_pipeline,
:success_pipeline
- ]
+ ].freeze
EXCLUDED_WATCHER_EVENTS = [
:success_pipeline
- ]
+ ].freeze
store :events, accessors: EMAIL_EVENTS, coder: JSON
diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb
index 0b9ebf1ffe2..f2f2fc1e32a 100644
--- a/app/models/pages_domain.rb
+++ b/app/models/pages_domain.rb
@@ -2,7 +2,7 @@ class PagesDomain < ActiveRecord::Base
belongs_to :project
validates :domain, hostname: true
- validates_uniqueness_of :domain, case_sensitive: false
+ validates :domain, uniqueness: { case_sensitive: false }
validates :certificate, certificate: true, allow_nil: true, allow_blank: true
validates :key, certificate_key: true, allow_nil: true, allow_blank: true
diff --git a/app/models/project.rb b/app/models/project.rb
index b45f22d94d9..aedd5bedcb9 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -22,7 +22,7 @@ class Project < ActiveRecord::Base
class BoardLimitExceeded < StandardError; end
NUMBER_OF_PERMITTED_BOARDS = 1
- UNKNOWN_IMPORT_URL = 'http://unknown.git'
+ UNKNOWN_IMPORT_URL = 'http://unknown.git'.freeze
cache_markdown_field :description, pipeline: :description
@@ -70,8 +70,7 @@ class Project < ActiveRecord::Base
after_validation :check_pending_delete
- ActsAsTaggableOn.strict_case_match = true
- acts_as_taggable_on :tags
+ acts_as_taggable
attr_accessor :new_default_branch
attr_accessor :old_path_with_namespace
@@ -172,9 +171,11 @@ class Project < ActiveRecord::Base
accepts_nested_attributes_for :project_feature
delegate :name, to: :owner, allow_nil: true, prefix: true
+ delegate :count, to: :forks, prefix: true
delegate :members, to: :team, prefix: true
delegate :add_user, to: :team
delegate :add_guest, :add_reporter, :add_developer, :add_master, to: :team
+ delegate :empty_repo?, to: :repository
# Validations
validates :creator, presence: true, on: :create
@@ -191,8 +192,8 @@ class Project < ActiveRecord::Base
format: { with: Gitlab::Regex.project_path_regex,
message: Gitlab::Regex.project_path_regex_message }
validates :namespace, presence: true
- validates_uniqueness_of :name, scope: :namespace_id
- validates_uniqueness_of :path, scope: :namespace_id
+ validates :name, uniqueness: { scope: :namespace_id }
+ validates :path, uniqueness: { scope: :namespace_id }
validates :import_url, addressable_url: true, if: :external_import?
validates :star_count, numericality: { greater_than_or_equal_to: 0 }
validate :check_limit, on: :create
@@ -214,6 +215,8 @@ class Project < ActiveRecord::Base
# Scopes
default_scope { where(pending_delete: false) }
+ scope :with_deleted, -> { unscope(where: :pending_delete) }
+
scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) }
scope :sorted_by_stars, -> { reorder('projects.star_count DESC') }
@@ -228,7 +231,12 @@ class Project < ActiveRecord::Base
scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') }
scope :with_statistics, -> { includes(:statistics) }
scope :with_shared_runners, -> { where(shared_runners_enabled: true) }
- scope :inside_path, ->(path) { joins(:route).where('routes.path LIKE ?', "#{path}/%") }
+ scope :inside_path, ->(path) do
+ # We need routes alias rs for JOIN so it does not conflict with
+ # includes(:route) which we use in ProjectsFinder.
+ joins("INNER JOIN routes rs ON rs.source_id = projects.id AND rs.source_type = 'Project'").
+ where('rs.path LIKE ?', "#{path}/%")
+ end
# "enabled" here means "not disabled". It includes private features!
scope :with_feature_enabled, ->(feature) {
@@ -351,7 +359,7 @@ class Project < ActiveRecord::Base
end
def reference_pattern
- name_pattern = Gitlab::Regex::NAMESPACE_REGEX_STR
+ name_pattern = Gitlab::Regex::FULL_NAMESPACE_REGEX_STR
%r{
((?<namespace>#{name_pattern})\/)?
@@ -446,13 +454,14 @@ class Project < ActiveRecord::Base
end
def add_import_job
- if forked?
- job_id = RepositoryForkWorker.perform_async(id, forked_from_project.repository_storage_path,
- forked_from_project.path_with_namespace,
- self.namespace.path)
- else
- job_id = RepositoryImportWorker.perform_async(self.id)
- end
+ job_id =
+ if forked?
+ RepositoryForkWorker.perform_async(id, forked_from_project.repository_storage_path,
+ forked_from_project.path_with_namespace,
+ self.namespace.full_path)
+ else
+ RepositoryImportWorker.perform_async(self.id)
+ end
if job_id
Rails.logger.info "Import job started for #{path_with_namespace} with job ID #{job_id}"
@@ -464,7 +473,7 @@ class Project < ActiveRecord::Base
def reset_cache_and_import_attrs
ProjectCacheWorker.perform_async(self.id)
- self.import_data.destroy if self.import_data
+ self.import_data&.destroy
end
def import_url=(value)
@@ -545,7 +554,7 @@ class Project < ActiveRecord::Base
end
def check_limit
- unless creator.can_create_project? or namespace.kind == 'group'
+ unless creator.can_create_project? || namespace.kind == 'group'
projects_limit = creator.projects_limit
if projects_limit == 0
@@ -810,26 +819,6 @@ class Project < ActiveRecord::Base
end
end
- def name_with_namespace
- @name_with_namespace ||= begin
- if namespace
- namespace.human_name + ' / ' + name
- else
- name
- end
- end
- end
- alias_method :human_name, :name_with_namespace
-
- def full_path
- if namespace && path
- namespace.full_path + '/' + path
- else
- path
- end
- end
- alias_method :path_with_namespace, :full_path
-
def execute_hooks(data, hooks_scope = :push_hooks)
hooks.send(hooks_scope).each do |hook|
hook.async_execute(data, hooks_scope.to_s)
@@ -850,10 +839,6 @@ class Project < ActiveRecord::Base
false
end
- def empty_repo?
- repository.empty_repo?
- end
-
def repo
repository.raw
end
@@ -862,10 +847,6 @@ class Project < ActiveRecord::Base
gitlab_shell.url_to_repo(path_with_namespace)
end
- def namespace_dir
- namespace.try(:path) || ''
- end
-
def repo_exists?
@repo_exists ||= repository.exists?
rescue
@@ -914,8 +895,8 @@ class Project < ActiveRecord::Base
def rename_repo
path_was = previous_changes['path'].first
- old_path_with_namespace = File.join(namespace_dir, path_was)
- new_path_with_namespace = File.join(namespace_dir, path)
+ old_path_with_namespace = File.join(namespace.full_path, path_was)
+ new_path_with_namespace = File.join(namespace.full_path, path)
Rails.logger.error "Attempting to rename #{old_path_with_namespace} -> #{new_path_with_namespace}"
@@ -957,8 +938,8 @@ class Project < ActiveRecord::Base
Gitlab::AppLogger.info "Project was renamed: #{old_path_with_namespace} -> #{new_path_with_namespace}"
- Gitlab::UploadsTransfer.new.rename_project(path_was, path, namespace.path)
- Gitlab::PagesTransfer.new.rename_project(path_was, path, namespace.path)
+ Gitlab::UploadsTransfer.new.rename_project(path_was, path, namespace.full_path)
+ Gitlab::PagesTransfer.new.rename_project(path_was, path, namespace.full_path)
end
# Expires various caches before a project is renamed.
@@ -1041,10 +1022,6 @@ class Project < ActiveRecord::Base
forked? && project == forked_from_project
end
- def forks_count
- forks.count
- end
-
def origin_merge_requests
merge_requests.where(source_project_id: self.id)
end
@@ -1165,19 +1142,25 @@ class Project < ActiveRecord::Base
end
def pages_url
+ subdomain, _, url_path = full_path.partition('/')
+
# The hostname always needs to be in downcased
# All web servers convert hostname to lowercase
- host = "#{namespace.path}.#{Settings.pages.host}".downcase
+ host = "#{subdomain}.#{Settings.pages.host}".downcase
# The host in URL always needs to be downcased
url = Gitlab.config.pages.url.sub(/^https?:\/\//) do |prefix|
- "#{prefix}#{namespace.path}."
+ "#{prefix}#{subdomain}."
end.downcase
# If the project path is the same as host, we serve it as group page
- return url if host == path
+ return url if host == url_path
+
+ "#{url}/#{url_path}"
+ end
- "#{url}/#{path}"
+ def pages_subdomain
+ full_path.partition('/').first
end
def pages_path
@@ -1194,8 +1177,8 @@ class Project < ActiveRecord::Base
# 3. We asynchronously remove pages with force
temp_path = "#{path}.#{SecureRandom.hex}.deleted"
- if Gitlab::PagesTransfer.new.rename_project(path, temp_path, namespace.path)
- PagesWorker.perform_in(5.minutes, :remove, namespace.path, temp_path)
+ if Gitlab::PagesTransfer.new.rename_project(path, temp_path, namespace.full_path)
+ PagesWorker.perform_in(5.minutes, :remove, namespace.full_path, temp_path)
end
end
@@ -1245,7 +1228,7 @@ class Project < ActiveRecord::Base
end
def ensure_dir_exist
- gitlab_shell.add_namespace(repository_storage_path, namespace.path)
+ gitlab_shell.add_namespace(repository_storage_path, namespace.full_path)
end
def predefined_variables
@@ -1253,7 +1236,7 @@ class Project < ActiveRecord::Base
{ key: 'CI_PROJECT_ID', value: id.to_s, public: true },
{ key: 'CI_PROJECT_NAME', value: path, public: true },
{ key: 'CI_PROJECT_PATH', value: path_with_namespace, public: true },
- { key: 'CI_PROJECT_NAMESPACE', value: namespace.path, public: true },
+ { key: 'CI_PROJECT_NAMESPACE', value: namespace.full_path, public: true },
{ key: 'CI_PROJECT_URL', value: web_url, public: true }
]
end
@@ -1328,6 +1311,18 @@ class Project < ActiveRecord::Base
map.public_path_for_source_path(path)
end
+ def parent
+ namespace
+ end
+
+ def parent_changed?
+ namespace_id_changed?
+ end
+
+ alias_method :name_with_namespace, :full_name
+ alias_method :human_name, :full_name
+ alias_method :path_with_namespace, :full_path
+
private
def cross_namespace_reference?(from)
@@ -1366,10 +1361,6 @@ class Project < ActiveRecord::Base
raise BoardLimitExceeded, 'Number of permitted boards exceeded' if boards.size >= NUMBER_OF_PERMITTED_BOARDS
end
- def full_path_changed?
- path_changed? || namespace_id_changed?
- end
-
def update_project_statistics
stats = statistics || build_statistics
stats.update(namespace_id: namespace_id)
diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
index 03194fc2141..e3ef4919b28 100644
--- a/app/models/project_feature.rb
+++ b/app/models/project_feature.rb
@@ -18,7 +18,7 @@ class ProjectFeature < ActiveRecord::Base
PRIVATE = 10
ENABLED = 20
- FEATURES = %i(issues merge_requests wiki snippets builds repository)
+ FEATURES = %i(issues merge_requests wiki snippets builds repository).freeze
class << self
def access_level_attribute(feature)
diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb
index 0956c4a4ede..5fb95050b83 100644
--- a/app/models/project_services/buildkite_service.rb
+++ b/app/models/project_services/buildkite_service.rb
@@ -3,7 +3,7 @@ require "addressable/uri"
class BuildkiteService < CiService
include ReactiveService
- ENDPOINT = "https://buildkite.com"
+ ENDPOINT = "https://buildkite.com".freeze
prop_accessor :project_url, :token
boolean_accessor :enable_ssl_verification
diff --git a/app/models/project_services/chat_message/base_message.rb b/app/models/project_services/chat_message/base_message.rb
index a03605d01fb..86d271a3f69 100644
--- a/app/models/project_services/chat_message/base_message.rb
+++ b/app/models/project_services/chat_message/base_message.rb
@@ -30,5 +30,9 @@ module ChatMessage
def attachment_color
'#345'
end
+
+ def link(text, url)
+ "[#{text}](#{url})"
+ end
end
end
diff --git a/app/models/project_services/chat_message/build_message.rb b/app/models/project_services/chat_message/build_message.rb
index 53e35cb21bf..c776e0a20c4 100644
--- a/app/models/project_services/chat_message/build_message.rb
+++ b/app/models/project_services/chat_message/build_message.rb
@@ -7,7 +7,11 @@ module ChatMessage
attr_reader :project_name
attr_reader :project_url
attr_reader :user_name
+ attr_reader :user_url
attr_reader :duration
+ attr_reader :stage
+ attr_reader :build_id
+ attr_reader :build_name
def initialize(params)
@sha = params[:sha]
@@ -17,7 +21,11 @@ module ChatMessage
@project_url = params[:project_url]
@status = params[:commit][:status]
@user_name = params[:commit][:author_name]
+ @user_url = params[:commit][:author_url]
@duration = params[:commit][:duration]
+ @stage = params[:build_stage]
+ @build_name = params[:build_name]
+ @build_id = params[:build_id]
end
def pretext
@@ -35,7 +43,19 @@ module ChatMessage
private
def message
- "#{project_link}: Commit #{commit_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status} in #{duration} #{'second'.pluralize(duration)}"
+ "#{project_link}: Commit #{commit_link} of #{branch_link} #{ref_type} by #{user_link} #{humanized_status} on build #{build_link} of stage #{stage} in #{duration} #{'second'.pluralize(duration)}"
+ end
+
+ def build_url
+ "#{project_url}/builds/#{build_id}"
+ end
+
+ def build_link
+ link(build_name, build_url)
+ end
+
+ def user_link
+ link(user_name, user_url)
end
def format(string)
@@ -64,11 +84,11 @@ module ChatMessage
end
def branch_link
- "[#{ref}](#{branch_url})"
+ link(ref, branch_url)
end
def project_link
- "[#{project_name}](#{project_url})"
+ link(project_name, project_url)
end
def commit_url
@@ -76,7 +96,7 @@ module ChatMessage
end
def commit_link
- "[#{Commit.truncate_sha(sha)}](#{commit_url})"
+ link(Commit.truncate_sha(sha), commit_url)
end
end
end
diff --git a/app/models/project_services/chat_message/issue_message.rb b/app/models/project_services/chat_message/issue_message.rb
index 14fd64e5332..791e5b0cec7 100644
--- a/app/models/project_services/chat_message/issue_message.rb
+++ b/app/models/project_services/chat_message/issue_message.rb
@@ -51,15 +51,16 @@ module ChatMessage
title: issue_title,
title_link: issue_url,
text: format(description),
- color: "#C95823" }]
+ color: "#C95823"
+ }]
end
def project_link
- "[#{project_name}](#{project_url})"
+ link(project_name, project_url)
end
def issue_link
- "[#{issue_title}](#{issue_url})"
+ link(issue_title, issue_url)
end
def issue_title
diff --git a/app/models/project_services/chat_message/merge_message.rb b/app/models/project_services/chat_message/merge_message.rb
index ab5e8b24167..5e5efca7bec 100644
--- a/app/models/project_services/chat_message/merge_message.rb
+++ b/app/models/project_services/chat_message/merge_message.rb
@@ -42,7 +42,7 @@ module ChatMessage
end
def project_link
- "[#{project_name}](#{project_url})"
+ link(project_name, project_url)
end
def merge_request_message
@@ -50,7 +50,7 @@ module ChatMessage
end
def merge_request_link
- "[merge request !#{merge_request_id}](#{merge_request_url})"
+ link("merge request !#{merge_request_id}", merge_request_url)
end
def merge_request_url
diff --git a/app/models/project_services/chat_message/note_message.rb b/app/models/project_services/chat_message/note_message.rb
index ca1d7207034..552113bac29 100644
--- a/app/models/project_services/chat_message/note_message.rb
+++ b/app/models/project_services/chat_message/note_message.rb
@@ -3,10 +3,9 @@ module ChatMessage
attr_reader :message
attr_reader :user_name
attr_reader :project_name
- attr_reader :project_link
+ attr_reader :project_url
attr_reader :note
attr_reader :note_url
- attr_reader :title
def initialize(params)
params = HashWithIndifferentAccess.new(params)
@@ -69,15 +68,15 @@ module ChatMessage
end
def description_message
- [{ text: format(@note), color: attachment_color }]
+ [{ text: format(note), color: attachment_color }]
end
def project_link
- "[#{@project_name}](#{@project_url})"
+ link(project_name, project_url)
end
def commented_on_message(target, title)
- @message = "#{@user_name} [commented on #{target}](#{@note_url}) in #{project_link}: *#{title}*"
+ @message = "#{user_name} #{link('commented on ' + target, note_url)} in #{project_link}: *#{title}*"
end
end
end
diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb
index 0a217d8caba..2717c240f05 100644
--- a/app/models/project_services/drone_ci_service.rb
+++ b/app/models/project_services/drone_ci_service.rb
@@ -12,7 +12,7 @@ class DroneCiService < CiService
def compose_service_hook
hook = service_hook || build_service_hook
# If using a service template, project may not be available
- hook.url = [drone_url, "/api/hook", "?owner=#{project.namespace.path}", "&name=#{project.path}", "&access_token=#{token}"].join if project
+ hook.url = [drone_url, "/api/hook", "?owner=#{project.namespace.full_path}", "&name=#{project.path}", "&access_token=#{token}"].join if project
hook.enable_ssl_verification = !!enable_ssl_verification
hook.save
end
@@ -38,8 +38,8 @@ class DroneCiService < CiService
def commit_status_path(sha, ref)
url = [drone_url,
- "gitlab/#{project.namespace.path}/#{project.path}/commits/#{sha}",
- "?branch=#{URI::encode(ref.to_s)}&access_token=#{token}"]
+ "gitlab/#{project.full_path}/commits/#{sha}",
+ "?branch=#{URI.encode(ref.to_s)}&access_token=#{token}"]
URI.join(*url).to_s
end
@@ -52,7 +52,7 @@ class DroneCiService < CiService
response = HTTParty.get(commit_status_path(sha, ref), verify: enable_ssl_verification)
status =
- if response.code == 200 and response['status']
+ if response.code == 200 && response['status']
case response['status']
when 'killed'
:canceled
@@ -73,8 +73,8 @@ class DroneCiService < CiService
def build_page(sha, ref)
url = [drone_url,
- "gitlab/#{project.namespace.path}/#{project.path}/redirect/commits/#{sha}",
- "?branch=#{URI::encode(ref.to_s)}"]
+ "gitlab/#{project.full_path}/redirect/commits/#{sha}",
+ "?branch=#{URI.encode(ref.to_s)}"]
URI.join(*url).to_s
end
@@ -114,7 +114,7 @@ class DroneCiService < CiService
end
def merge_request_valid?(data)
- ['opened', 'reopened'].include?(data[:object_attributes][:state]) &&
+ %w(opened reopened).include?(data[:object_attributes][:state]) &&
data[:object_attributes][:merge_status] == 'unchecked'
end
end
diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb
deleted file mode 100644
index bbc312f5215..00000000000
--- a/app/models/project_services/gitlab_ci_service.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# TODO(ayufan): The GitLabCiService is deprecated and the type should be removed when the database entries are removed
-class GitlabCiService < CiService
- # We override the active accessor to always make GitLabCiService disabled
- # Otherwise the GitLabCiService can be picked, but should never be since it's deprecated
- def active
- false
- end
-end
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index 72da219df28..c4142c38b2f 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -6,7 +6,7 @@ class HipchatService < Service
a b i strong em br img pre code
table th tr td caption colgroup col thead tbody tfoot
ul ol li dl dt dd
- ]
+ ].freeze
prop_accessor :token, :room, :server, :color, :api_version
boolean_accessor :notify_only_broken_builds, :notify
@@ -36,7 +36,7 @@ class HipchatService < Service
{ type: 'text', name: 'token', placeholder: 'Room token' },
{ type: 'text', name: 'room', placeholder: 'Room name or ID' },
{ type: 'checkbox', name: 'notify' },
- { type: 'select', name: 'color', choices: ['yellow', 'red', 'green', 'purple', 'gray', 'random'] },
+ { type: 'select', name: 'color', choices: %w(yellow red green purple gray random) },
{ type: 'text', name: 'api_version',
placeholder: 'Leave blank for default (v2)' },
{ type: 'text', name: 'server',
diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb
index 5d93064f9b3..c62bb4fa120 100644
--- a/app/models/project_services/irker_service.rb
+++ b/app/models/project_services/irker_service.rb
@@ -33,7 +33,8 @@ class IrkerService < Service
end
def settings
- { server_host: server_host.present? ? server_host : 'localhost',
+ {
+ server_host: server_host.present? ? server_host : 'localhost',
server_port: server_port.present? ? server_port : 6659
}
end
@@ -96,7 +97,7 @@ class IrkerService < Service
rescue URI::InvalidURIError
end
- unless uri.present? and default_irc_uri.nil?
+ unless uri.present? && default_irc_uri.nil?
begin
new_recipient = URI.join(default_irc_uri, '/', recipient).to_s
uri = consider_uri(URI.parse(new_recipient))
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 80d002f9c32..eef403dba92 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -250,21 +250,11 @@ class JiraService < IssueTrackerService
end
end
- # Build remote link on JIRA properties
- # Icons here must be available on WEB so JIRA can read the URL
- # We are using a open word graphics icon which have LGPL license
def build_remote_link_props(url:, title:, resolved: false)
status = {
resolved: resolved
}
- if resolved
- status[:icon] = {
- title: 'Closed',
- url16x16: 'http://www.openwebgraphics.com/resources/data/1768/16x16_apply.png'
- }
- end
-
{
GlobalID: 'GitLab',
object: {
diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb
index f2f019c43bb..9819e723fe8 100644
--- a/app/models/project_services/kubernetes_service.rb
+++ b/app/models/project_services/kubernetes_service.rb
@@ -3,7 +3,7 @@ class KubernetesService < DeploymentService
include Gitlab::Kubernetes
include ReactiveCaching
- self.reactive_cache_key = ->(service) { [ service.class.model_name.singular, service.project_id ] }
+ self.reactive_cache_key = ->(service) { [service.class.model_name.singular, service.project_id] }
# Namespace defaults to the project path, but can be overridden in case that
# is an invalid or inappropriate name
@@ -62,23 +62,19 @@ class KubernetesService < DeploymentService
{ type: 'text',
name: 'namespace',
title: 'Kubernetes namespace',
- placeholder: 'Kubernetes namespace',
- },
+ placeholder: 'Kubernetes namespace' },
{ type: 'text',
name: 'api_url',
title: 'API URL',
- placeholder: 'Kubernetes API URL, like https://kube.example.com/',
- },
+ placeholder: 'Kubernetes API URL, like https://kube.example.com/' },
{ type: 'text',
name: 'token',
title: 'Service token',
- placeholder: 'Service token',
- },
+ placeholder: 'Service token' },
{ type: 'textarea',
name: 'ca_pem',
title: 'Custom CA bundle',
- placeholder: 'Certificate Authority bundle (PEM format)',
- },
+ placeholder: 'Certificate Authority bundle (PEM format)' },
]
end
@@ -167,7 +163,7 @@ class KubernetesService < DeploymentService
url = URI.parse(api_url)
prefix = url.path.sub(%r{/+\z}, '')
- url.path = [ prefix, *parts ].join("/")
+ url.path = [prefix, *parts].join("/")
url.to_s
end
diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb
index 9cc642591f4..d86f4f6f448 100644
--- a/app/models/project_services/pivotaltracker_service.rb
+++ b/app/models/project_services/pivotaltracker_service.rb
@@ -1,7 +1,7 @@
class PivotaltrackerService < Service
include HTTParty
- API_ENDPOINT = 'https://www.pivotaltracker.com/services/v5/source_commits'
+ API_ENDPOINT = 'https://www.pivotaltracker.com/services/v5/source_commits'.freeze
prop_accessor :token, :restrict_to_branch
validates :token, presence: true, if: :activated?
diff --git a/app/models/project_services/pushover_service.rb b/app/models/project_services/pushover_service.rb
index a963d27a376..3e618a8dbf1 100644
--- a/app/models/project_services/pushover_service.rb
+++ b/app/models/project_services/pushover_service.rb
@@ -29,25 +29,24 @@ class PushoverService < Service
['Normal Priority', 0],
['High Priority', 1]
],
- default_choice: 0
- },
+ default_choice: 0 },
{ type: 'select', name: 'sound', choices:
[
['Device default sound', nil],
['Pushover (default)', 'pushover'],
- ['Bike', 'bike'],
- ['Bugle', 'bugle'],
+ %w(Bike bike),
+ %w(Bugle bugle),
['Cash Register', 'cashregister'],
- ['Classical', 'classical'],
- ['Cosmic', 'cosmic'],
- ['Falling', 'falling'],
- ['Gamelan', 'gamelan'],
- ['Incoming', 'incoming'],
- ['Intermission', 'intermission'],
- ['Magic', 'magic'],
- ['Mechanical', 'mechanical'],
+ %w(Classical classical),
+ %w(Cosmic cosmic),
+ %w(Falling falling),
+ %w(Gamelan gamelan),
+ %w(Incoming incoming),
+ %w(Intermission intermission),
+ %w(Magic magic),
+ %w(Mechanical mechanical),
['Piano Bar', 'pianobar'],
- ['Siren', 'siren'],
+ %w(Siren siren),
['Space Alarm', 'spacealarm'],
['Tug Boat', 'tugboat'],
['Alien Alarm (long)', 'alien'],
@@ -56,8 +55,7 @@ class PushoverService < Service
['Pushover Echo (long)', 'echo'],
['Up Down (long)', 'updown'],
['None (silent)', 'none']
- ]
- },
+ ] },
]
end
@@ -72,13 +70,14 @@ class PushoverService < Service
before = data[:before]
after = data[:after]
- if Gitlab::Git.blank_ref?(before)
- message = "#{data[:user_name]} pushed new branch \"#{ref}\"."
- elsif Gitlab::Git.blank_ref?(after)
- message = "#{data[:user_name]} deleted branch \"#{ref}\"."
- else
- message = "#{data[:user_name]} push to branch \"#{ref}\"."
- end
+ message =
+ if Gitlab::Git.blank_ref?(before)
+ "#{data[:user_name]} pushed new branch \"#{ref}\"."
+ elsif Gitlab::Git.blank_ref?(after)
+ "#{data[:user_name]} deleted branch \"#{ref}\"."
+ else
+ "#{data[:user_name]} push to branch \"#{ref}\"."
+ end
if data[:total_commits_count] > 0
message << "\nTotal commits count: #{data[:total_commits_count]}"
@@ -97,7 +96,7 @@ class PushoverService < Service
# Sound parameter MUST NOT be sent to API if not selected
if sound
- pushover_data.merge!(sound: sound)
+ pushover_data[:sound] = sound
end
PushoverService.post('/messages.json', body: pushover_data)
diff --git a/app/models/project_snippet.rb b/app/models/project_snippet.rb
index 9bb456eee24..25b5d777641 100644
--- a/app/models/project_snippet.rb
+++ b/app/models/project_snippet.rb
@@ -9,8 +9,4 @@ class ProjectSnippet < Snippet
participant :author
participant :notes_with_associations
-
- def check_for_spam?
- super && project.public?
- end
end
diff --git a/app/models/project_statistics.rb b/app/models/project_statistics.rb
index 06abd406523..aeaf63abab9 100644
--- a/app/models/project_statistics.rb
+++ b/app/models/project_statistics.rb
@@ -4,7 +4,7 @@ class ProjectStatistics < ActiveRecord::Base
before_save :update_storage_size
- STORAGE_COLUMNS = [:repository_size, :lfs_objects_size, :build_artifacts_size]
+ STORAGE_COLUMNS = [:repository_size, :lfs_objects_size, :build_artifacts_size].freeze
STATISTICS_COLUMNS = [:commit_count] + STORAGE_COLUMNS
def total_repository_size
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index 9db96347322..9891f5edf41 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -5,7 +5,7 @@ class ProjectWiki
'Markdown' => :markdown,
'RDoc' => :rdoc,
'AsciiDoc' => :asciidoc
- } unless defined?(MARKUPS)
+ }.freeze unless defined?(MARKUPS)
class CouldNotCreateWikiError < StandardError; end
@@ -19,6 +19,9 @@ class ProjectWiki
@user = user
end
+ delegate :empty?, to: :pages
+ delegate :repository_storage_path, to: :project
+
def path
@project.path + '.wiki'
end
@@ -60,10 +63,6 @@ class ProjectWiki
!!repository.exists?
end
- def empty?
- pages.empty?
- end
-
# Returns an Array of Gitlab WikiPage instances or an
# empty Array if this Wiki has no pages.
def pages
diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb
index 6240912a6e1..39e979ef15b 100644
--- a/app/models/protected_branch.rb
+++ b/app/models/protected_branch.rb
@@ -8,8 +8,8 @@ class ProtectedBranch < ActiveRecord::Base
has_many :merge_access_levels, dependent: :destroy
has_many :push_access_levels, dependent: :destroy
- validates_length_of :merge_access_levels, is: 1, message: "are restricted to a single instance per protected branch."
- validates_length_of :push_access_levels, is: 1, message: "are restricted to a single instance per protected branch."
+ validates :merge_access_levels, length: { is: 1, message: "are restricted to a single instance per protected branch." }
+ validates :push_access_levels, length: { is: 1, message: "are restricted to a single instance per protected branch." }
accepts_nested_attributes_for :push_access_levels
accepts_nested_attributes_for :merge_access_levels
diff --git a/app/models/repository.rb b/app/models/repository.rb
index d2d92a064a4..cd2568ad445 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -18,7 +18,7 @@ class Repository
CACHED_METHODS = %i(size commit_count readme version contribution_guide
changelog license_blob license_key gitignore koding_yml
gitlab_ci_yml branch_names tag_names branch_count
- tag_count avatar exists? empty? root_ref)
+ tag_count avatar exists? empty? root_ref).freeze
# Certain method caches should be refreshed when certain types of files are
# changed. This Hash maps file types (as returned by Gitlab::FileDetector) to
@@ -33,7 +33,7 @@ class Repository
koding: :koding_yml,
gitlab_ci: :gitlab_ci_yml,
avatar: :avatar
- }
+ }.freeze
# Wraps around the given method and caches its output in Redis and an instance
# variable.
@@ -487,9 +487,7 @@ class Repository
end
cache_method :exists?
- def empty?
- raw_repository.empty?
- end
+ delegate :empty?, to: :raw_repository
cache_method :empty?
# The size of this repository in megabytes.
@@ -508,9 +506,7 @@ class Repository
end
cache_method :branch_names, fallback: []
- def tag_names
- raw_repository.tag_names
- end
+ delegate :tag_names, to: :raw_repository
cache_method :tag_names, fallback: []
def branch_count
@@ -892,7 +888,7 @@ class Repository
def get_committer_and_author(user, email: nil, name: nil)
committer = user_to_committer(user)
- author = Gitlab::Git::committer_hash(email: email, name: name) || committer
+ author = Gitlab::Git.committer_hash(email: email, name: name) || committer
{
author: author,
@@ -1230,6 +1226,14 @@ class Repository
action[:content]
end
+ detect = CharlockHolmes::EncodingDetector.new.detect(content) if content
+
+ unless detect && detect[:type] == :binary
+ # When writing to the repo directly as we are doing here,
+ # the `core.autocrlf` config isn't taken into account.
+ content.gsub!("\r\n", "\n") if self.autocrlf
+ end
+
oid = rugged.write(content, :blob)
index.add(path: path, oid: oid, mode: mode)
diff --git a/app/models/route.rb b/app/models/route.rb
index dd171fdb069..73574a6206b 100644
--- a/app/models/route.rb
+++ b/app/models/route.rb
@@ -8,16 +8,27 @@ class Route < ActiveRecord::Base
presence: true,
uniqueness: { case_sensitive: false }
- after_update :rename_descendants, if: :path_changed?
+ after_update :rename_descendants
def rename_descendants
- # We update each row separately because MySQL does not have regexp_replace.
- # rubocop:disable Rails/FindEach
- Route.where('path LIKE ?', "#{path_was}/%").each do |route|
- # Note that update column skips validation and callbacks.
- # We need this to avoid recursive call of rename_descendants method
- route.update_column(:path, route.path.sub(path_was, path))
+ if path_changed? || name_changed?
+ descendants = Route.where('path LIKE ?', "#{path_was}/%")
+
+ descendants.each do |route|
+ attributes = {}
+
+ if path_changed? && route.path.present?
+ attributes[:path] = route.path.sub(path_was, path)
+ end
+
+ if name_changed? && route.name.present?
+ attributes[:name] = route.name.sub(name_was, name)
+ end
+
+ # Note that update_columns skips validation and callbacks.
+ # We need this to avoid recursive call of rename_descendants method
+ route.update_columns(attributes) unless attributes.empty?
+ end
end
- # rubocop:enable Rails/FindEach
end
end
diff --git a/app/models/service.rb b/app/models/service.rb
index 043be222f3a..facaaf9b331 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -27,7 +27,7 @@ class Service < ActiveRecord::Base
validates :project_id, presence: true, unless: Proc.new { |service| service.template? }
- scope :visible, -> { where.not(type: ['GitlabIssueTrackerService', 'GitlabCiService']) }
+ scope :visible, -> { where.not(type: 'GitlabIssueTrackerService') }
scope :issue_trackers, -> { where(category: 'issue_tracker') }
scope :external_wikis, -> { where(type: 'ExternalWikiService').active }
scope :active, -> { where(active: true) }
diff --git a/app/models/todo.rb b/app/models/todo.rb
index 2adf494ce11..47789a21133 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -1,12 +1,13 @@
class Todo < ActiveRecord::Base
include Sortable
- ASSIGNED = 1
- MENTIONED = 2
- BUILD_FAILED = 3
- MARKED = 4
- APPROVAL_REQUIRED = 5 # This is an EE-only feature
- UNMERGEABLE = 6
+ ASSIGNED = 1
+ MENTIONED = 2
+ BUILD_FAILED = 3
+ MARKED = 4
+ APPROVAL_REQUIRED = 5 # This is an EE-only feature
+ UNMERGEABLE = 6
+ DIRECTLY_ADDRESSED = 7
ACTION_NAMES = {
ASSIGNED => :assigned,
@@ -14,8 +15,9 @@ class Todo < ActiveRecord::Base
BUILD_FAILED => :build_failed,
MARKED => :marked,
APPROVAL_REQUIRED => :approval_required,
- UNMERGEABLE => :unmergeable
- }
+ UNMERGEABLE => :unmergeable,
+ DIRECTLY_ADDRESSED => :directly_addressed
+ }.freeze
belongs_to :author, class_name: "User"
belongs_to :note
diff --git a/app/models/user.rb b/app/models/user.rb
index f64d0c17a45..40264401b53 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -21,7 +21,6 @@ class User < ActiveRecord::Base
default_value_for :can_create_team, false
default_value_for :hide_no_ssh_key, false
default_value_for :hide_no_password, false
- default_value_for :theme_id, gitlab_config.default_theme
attr_encrypted :otp_secret,
key: Gitlab::Application.secrets.otp_key_base,
@@ -51,7 +50,12 @@ class User < ActiveRecord::Base
has_one :namespace, -> { where type: nil }, dependent: :destroy, foreign_key: :owner_id
# Profile
- has_many :keys, dependent: :destroy
+ has_many :keys, -> do
+ type = Key.arel_table[:type]
+ where(type.not_eq('DeployKey').or(type.eq(nil)))
+ end, dependent: :destroy
+ has_many :deploy_keys, -> { where(type: 'DeployKey') }, dependent: :destroy
+
has_many :emails, dependent: :destroy
has_many :personal_access_tokens, dependent: :destroy
has_many :identities, dependent: :destroy, autosave: true
@@ -77,7 +81,6 @@ class User < ActiveRecord::Base
has_many :authorized_projects, through: :project_authorizations, source: :project
has_many :snippets, dependent: :destroy, foreign_key: :author_id
- has_many :issues, dependent: :destroy, foreign_key: :author_id
has_many :notes, dependent: :destroy, foreign_key: :author_id
has_many :merge_requests, dependent: :destroy, foreign_key: :author_id
has_many :events, dependent: :destroy, foreign_key: :author_id
@@ -95,12 +98,17 @@ class User < ActiveRecord::Base
has_many :assigned_issues, dependent: :nullify, foreign_key: :assignee_id, class_name: "Issue"
has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest"
+ # Issues that a user owns are expected to be moved to the "ghost" user before
+ # the user is destroyed. If the user owns any issues during deletion, this
+ # should be treated as an exceptional condition.
+ has_many :issues, dependent: :restrict_with_exception, foreign_key: :author_id
+
#
# Validations
#
# Note: devise :validatable above adds validations for :email and :password
validates :name, presence: true
- validates_confirmation_of :email
+ validates :email, confirmation: true
validates :notification_email, presence: true
validates :notification_email, email: true, if: ->(user) { user.notification_email != user.email }
validates :public_email, presence: true, uniqueness: true, email: true, allow_blank: true
@@ -116,10 +124,11 @@ class User < ActiveRecord::Base
validate :unique_email, if: ->(user) { user.email_changed? }
validate :owns_notification_email, if: ->(user) { user.notification_email_changed? }
validate :owns_public_email, if: ->(user) { user.public_email_changed? }
+ validate :ghost_users_must_be_blocked
validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
before_validation :generate_password, on: :create
- before_validation :signup_domain_valid?, on: :create
+ before_validation :signup_domain_valid?, on: :create, if: ->(user) { !user.created_by_id }
before_validation :sanitize_attrs
before_validation :set_notification_email, if: ->(user) { user.email_changed? }
before_validation :set_public_email, if: ->(user) { user.public_email_changed? }
@@ -167,6 +176,15 @@ class User < ActiveRecord::Base
def blocked?
true
end
+
+ def active_for_authentication?
+ false
+ end
+
+ def inactive_message
+ "Your account has been blocked. Please contact your GitLab " \
+ "administrator if you think this is an error."
+ end
end
end
@@ -305,7 +323,7 @@ class User < ActiveRecord::Base
def find_by_personal_access_token(token_string)
personal_access_token = PersonalAccessToken.active.find_by_token(token_string) if token_string
- personal_access_token.user if personal_access_token
+ personal_access_token&.user
end
# Returns a user for the given SSH key.
@@ -321,9 +339,15 @@ class User < ActiveRecord::Base
def reference_pattern
%r{
#{Regexp.escape(reference_prefix)}
- (?<user>#{Gitlab::Regex::NAMESPACE_REGEX_STR})
+ (?<user>#{Gitlab::Regex::FULL_NAMESPACE_REGEX_STR})
}x
end
+
+ # Return (create if necessary) the ghost user. The ghost user
+ # owns records previously belonging to deleted users.
+ def ghost
+ User.find_by_ghost(true) || create_ghost_user
+ end
end
#
@@ -422,6 +446,12 @@ class User < ActiveRecord::Base
errors.add(:public_email, "is not an email you own") unless all_emails.include?(public_email)
end
+ def ghost_users_must_be_blocked
+ if ghost? && !blocked?
+ errors.add(:ghost, 'cannot be enabled for a user who is not blocked.')
+ end
+ end
+
def update_emails_with_primary_email
primary_email_record = emails.find_by(email: email)
if primary_email_record
@@ -567,8 +597,8 @@ class User < ActiveRecord::Base
if project.repository.branch_exists?(event.branch_name)
merge_requests = MergeRequest.where("created_at >= ?", event.created_at).
- where(source_project_id: project.id,
- source_branch: event.branch_name)
+ where(source_project_id: project.id,
+ source_branch: event.branch_name)
merge_requests.empty?
end
end
@@ -986,4 +1016,40 @@ class User < ActiveRecord::Base
super
end
end
+
+ def self.create_ghost_user
+ # Since we only want a single ghost user in an instance, we use an
+ # exclusive lease to ensure than this block is never run concurrently.
+ lease_key = "ghost_user_creation"
+ lease = Gitlab::ExclusiveLease.new(lease_key, timeout: 1.minute.to_i)
+
+ until uuid = lease.try_obtain
+ # Keep trying until we obtain the lease. To prevent hammering Redis too
+ # much we'll wait for a bit between retries.
+ sleep(1)
+ end
+
+ # Recheck if a ghost user is already present. One might have been
+ # added between the time we last checked (first line of this method)
+ # and the time we acquired the lock.
+ ghost_user = User.find_by_ghost(true)
+ return ghost_user if ghost_user.present?
+
+ uniquify = Uniquify.new
+
+ username = uniquify.string("ghost") { |s| User.find_by_username(s) }
+
+ email = uniquify.string(-> (n) { "ghost#{n}@example.com" }) do |s|
+ User.find_by_email(s)
+ end
+
+ bio = 'This is a "Ghost User", created to hold all issues authored by users that have since been deleted. This user cannot be removed.'
+
+ User.create(
+ username: username, password: Devise.friendly_token, bio: bio,
+ email: email, name: "Ghost User", state: :blocked, ghost: true
+ )
+ ensure
+ Gitlab::ExclusiveLease.cancel(lease_key, uuid)
+ end
end
diff --git a/app/models/wiki_directory.rb b/app/models/wiki_directory.rb
new file mode 100644
index 00000000000..9340fc2dbbe
--- /dev/null
+++ b/app/models/wiki_directory.rb
@@ -0,0 +1,18 @@
+class WikiDirectory
+ include ActiveModel::Validations
+
+ attr_accessor :slug, :pages
+
+ validates :slug, presence: true
+
+ def initialize(slug, pages = [])
+ @slug = slug
+ @pages = pages
+ end
+
+ # Relative path to the partial to be used when rendering collections
+ # of this object.
+ def to_partial_path
+ 'projects/wikis/wiki_directory'
+ end
+end
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index c3de278f5b7..2caebb496db 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -12,6 +12,32 @@ class WikiPage
ActiveModel::Name.new(self, nil, 'wiki')
end
+ # Sorts and groups pages by directory.
+ #
+ # pages - an array of WikiPage objects.
+ #
+ # Returns an array of WikiPage and WikiDirectory objects. The entries are
+ # sorted by alphabetical order (directories and pages inside each directory).
+ # Pages at the root level come before everything.
+ def self.group_by_directory(pages)
+ return [] if pages.blank?
+
+ pages.sort_by { |page| [page.directory, page.slug] }.
+ group_by(&:directory).
+ map do |dir, pages|
+ if dir.present?
+ WikiDirectory.new(dir, pages)
+ else
+ pages
+ end
+ end.
+ flatten
+ end
+
+ def self.unhyphenize(name)
+ name.gsub(/-+/, ' ')
+ end
+
def to_key
[:slug]
end
@@ -56,7 +82,7 @@ class WikiPage
# The formatted title of this page.
def title
if @attributes[:title]
- @attributes[:title].gsub(/-+/, ' ')
+ self.class.unhyphenize(@attributes[:title])
else
""
end
@@ -69,16 +95,17 @@ class WikiPage
# The raw content of this page.
def content
- @attributes[:content] ||= if @page
- @page.text_data
- end
+ @attributes[:content] ||= @page&.text_data
+ end
+
+ # The hierarchy of the directory this page is contained in.
+ def directory
+ wiki.page_title_and_dir(slug).last
end
# The processed/formatted content of this page.
def formatted_content
- @attributes[:formatted_content] ||= if @page
- @page.formatted_data
- end
+ @attributes[:formatted_content] ||= @page&.formatted_data
end
# The markup format for the page.
@@ -174,6 +201,16 @@ class WikiPage
end
end
+ # Relative path to the partial to be used when rendering collections
+ # of this object.
+ def to_partial_path
+ 'projects/wikis/wiki_page'
+ end
+
+ def id
+ page.version.to_s
+ end
+
private
def set_attributes
diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb
index b9f1c29c32e..e07b144355a 100644
--- a/app/policies/base_policy.rb
+++ b/app/policies/base_policy.rb
@@ -6,9 +6,7 @@ class BasePolicy
@cannot_set = cannot_set
end
- def size
- to_set.size
- end
+ delegate :size, to: :to_set
def self.empty
new(Set.new, Set.new)
diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb
index 03a2499e263..229846e368c 100644
--- a/app/policies/user_policy.rb
+++ b/app/policies/user_policy.rb
@@ -3,6 +3,14 @@ class UserPolicy < BasePolicy
def rules
can! :read_user if @user || !restricted_public_level?
+
+ if @user
+ if @user.admin? || @subject == @user
+ can! :destroy_user
+ end
+
+ cannot! :destroy_user if @subject.ghost?
+ end
end
def restricted_public_level?
diff --git a/app/serializers/analytics_stage_entity.rb b/app/serializers/analytics_stage_entity.rb
index a559d0850c4..69bf693de8d 100644
--- a/app/serializers/analytics_stage_entity.rb
+++ b/app/serializers/analytics_stage_entity.rb
@@ -2,6 +2,7 @@ class AnalyticsStageEntity < Grape::Entity
include EntityDateHelper
expose :title
+ expose :legend
expose :description
expose :median, as: :value do |stage|
diff --git a/app/serializers/environment_serializer.rb b/app/serializers/environment_serializer.rb
index 91955542f25..d0a60f134da 100644
--- a/app/serializers/environment_serializer.rb
+++ b/app/serializers/environment_serializer.rb
@@ -1,3 +1,55 @@
class EnvironmentSerializer < BaseSerializer
+ Item = Struct.new(:name, :size, :latest)
+
entity EnvironmentEntity
+
+ def within_folders
+ tap { @itemize = true }
+ end
+
+ def with_pagination(request, response)
+ tap { @paginator = Gitlab::Serializer::Pagination.new(request, response) }
+ end
+
+ def itemized?
+ @itemize
+ end
+
+ def paginated?
+ @paginator.present?
+ end
+
+ def represent(resource, opts = {})
+ if itemized?
+ itemize(resource).map do |item|
+ { name: item.name,
+ size: item.size,
+ latest: super(item.latest, opts) }
+ end
+ else
+ resource = @paginator.paginate(resource) if paginated?
+
+ super(resource, opts)
+ end
+ end
+
+ private
+
+ def itemize(resource)
+ items = resource.order('folder_name ASC')
+ .group('COALESCE(environment_type, name)')
+ .select('COALESCE(environment_type, name) AS folder_name',
+ 'COUNT(*) AS size', 'MAX(id) AS last_id')
+
+ # It makes a difference when you call `paginate` method, because
+ # although `page` is effective at the end, it calls counting methods
+ # immediately.
+ items = @paginator.paginate(items) if paginated?
+
+ environments = resource.where(id: items.map(&:last_id)).index_by(&:id)
+
+ items.map do |item|
+ Item.new(item.folder_name, item.size, environments[item.last_id])
+ end
+ end
end
diff --git a/app/serializers/pipeline_serializer.rb b/app/serializers/pipeline_serializer.rb
index b2de6c5832e..2bc6cf3266e 100644
--- a/app/serializers/pipeline_serializer.rb
+++ b/app/serializers/pipeline_serializer.rb
@@ -1,41 +1,25 @@
class PipelineSerializer < BaseSerializer
class InvalidResourceError < StandardError; end
- include API::Helpers::Pagination
- Struct.new('Pagination', :request, :response)
entity PipelineEntity
- def represent(resource, opts = {})
- if paginated?
- raise InvalidResourceError unless resource.respond_to?(:page)
-
- super(paginate(resource.includes(project: :namespace)), opts)
- else
- super(resource, opts)
- end
- end
-
- def paginated?
- defined?(@pagination)
- end
-
def with_pagination(request, response)
- tap { @pagination = Struct::Pagination.new(request, response) }
+ tap { @paginator = Gitlab::Serializer::Pagination.new(request, response) }
end
- private
-
- # Methods needed by `API::Helpers::Pagination`
- #
- def params
- @pagination.request.query_parameters
+ def paginated?
+ @paginator.present?
end
- def request
- @pagination.request
- end
+ def represent(resource, opts = {})
+ if resource.is_a?(ActiveRecord::Relation)
+ resource = resource.includes(project: :namespace)
+ end
- def header(header, value)
- @pagination.response.headers[header] = value
+ if paginated?
+ super(@paginator.paginate(resource), opts)
+ else
+ super(resource, opts)
+ end
end
end
diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb
index 5cb7a86a5ee..db82b8f6c30 100644
--- a/app/services/auth/container_registry_authentication_service.rb
+++ b/app/services/auth/container_registry_authentication_service.rb
@@ -2,7 +2,7 @@ module Auth
class ContainerRegistryAuthenticationService < BaseService
include Gitlab::CurrentSettings
- AUDIENCE = 'container_registry'
+ AUDIENCE = 'container_registry'.freeze
def execute(authentication_abilities:)
@authentication_abilities = authentication_abilities
diff --git a/app/services/base_service.rb b/app/services/base_service.rb
index 1a2bad77a02..745c2c4b681 100644
--- a/app/services/base_service.rb
+++ b/app/services/base_service.rb
@@ -1,4 +1,5 @@
class BaseService
+ include Gitlab::Allowable
include Gitlab::CurrentSettings
attr_accessor :project, :current_user, :params
@@ -7,10 +8,6 @@ class BaseService
@project, @current_user, @params = project, user, params.dup
end
- def can?(object, action, subject)
- Ability.allowed?(object, action, subject)
- end
-
def notification_service
NotificationService.new
end
@@ -31,9 +28,7 @@ class BaseService
SystemHooksService.new
end
- def repository
- project.repository
- end
+ delegate :repository, to: :project
# Add an error to the specified model for restricted visibility levels
def deny_visibility_level(model, denied_visibility_level = nil)
diff --git a/app/services/ci/create_pipeline_builds_service.rb b/app/services/ci/create_pipeline_builds_service.rb
index b7da3f8e7eb..70fb2c5e38f 100644
--- a/app/services/ci/create_pipeline_builds_service.rb
+++ b/app/services/ci/create_pipeline_builds_service.rb
@@ -10,9 +10,7 @@ module Ci
end
end
- def project
- pipeline.project
- end
+ delegate :project, to: :pipeline
private
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index e3bc9847200..38a85e9fc42 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -59,7 +59,8 @@ module Ci
private
def skip_ci?
- pipeline.git_commit_message =~ /\[(ci skip|skip ci)\]/i if pipeline.git_commit_message
+ return false unless pipeline.git_commit_message
+ pipeline.git_commit_message =~ /\[(ci[ _-]skip|skip[ _-]ci)\]/i
end
def commit
diff --git a/app/services/ci/retry_build_service.rb b/app/services/ci/retry_build_service.rb
new file mode 100644
index 00000000000..38ef323f6e5
--- /dev/null
+++ b/app/services/ci/retry_build_service.rb
@@ -0,0 +1,43 @@
+module Ci
+ class RetryBuildService < ::BaseService
+ CLONE_ATTRIBUTES = %i[pipeline project ref tag options commands name
+ allow_failure stage stage_idx trigger_request
+ yaml_variables when environment coverage_regex]
+ .freeze
+
+ REJECT_ATTRIBUTES = %i[id status user token coverage trace runner
+ artifacts_expire_at artifacts_file
+ artifacts_metadata artifacts_size
+ created_at updated_at started_at finished_at
+ queued_at erased_by erased_at].freeze
+
+ IGNORE_ATTRIBUTES = %i[type lock_version gl_project_id target_url
+ deploy job_id description].freeze
+
+ def execute(build)
+ reprocess(build).tap do |new_build|
+ build.pipeline.mark_as_processable_after_stage(build.stage_idx)
+
+ new_build.enqueue!
+
+ MergeRequests::AddTodoWhenBuildFailsService
+ .new(project, current_user)
+ .close(new_build)
+ end
+ end
+
+ def reprocess(build)
+ unless can?(current_user, :update_build, build)
+ raise Gitlab::Access::AccessDeniedError
+ end
+
+ attributes = CLONE_ATTRIBUTES.map do |attribute|
+ [attribute, build.send(attribute)]
+ end
+
+ attributes.push([:user, current_user])
+
+ project.builds.create(Hash[attributes])
+ end
+ end
+end
diff --git a/app/services/ci/retry_pipeline_service.rb b/app/services/ci/retry_pipeline_service.rb
new file mode 100644
index 00000000000..574561adc4c
--- /dev/null
+++ b/app/services/ci/retry_pipeline_service.rb
@@ -0,0 +1,28 @@
+module Ci
+ class RetryPipelineService < ::BaseService
+ include Gitlab::OptimisticLocking
+
+ def execute(pipeline)
+ unless can?(current_user, :update_pipeline, pipeline)
+ raise Gitlab::Access::AccessDeniedError
+ end
+
+ pipeline.builds.failed_or_canceled.find_each do |build|
+ next unless build.retryable?
+
+ Ci::RetryBuildService.new(project, current_user)
+ .reprocess(build)
+ end
+
+ pipeline.builds.skipped.find_each do |skipped|
+ retry_optimistic_lock(skipped) { |build| build.process }
+ end
+
+ MergeRequests::AddTodoWhenBuildFailsService
+ .new(project, current_user)
+ .close_all(pipeline)
+
+ pipeline.process!
+ end
+ end
+end
diff --git a/app/services/ci/update_runner_service.rb b/app/services/ci/update_runner_service.rb
new file mode 100644
index 00000000000..450ee7da1c9
--- /dev/null
+++ b/app/services/ci/update_runner_service.rb
@@ -0,0 +1,15 @@
+module Ci
+ class UpdateRunnerService
+ attr_reader :runner
+
+ def initialize(runner)
+ @runner = runner
+ end
+
+ def update(params)
+ runner.update(params).tap do |updated|
+ runner.tick_runner_queue if updated
+ end
+ end
+ end
+end
diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb
index 77459d8779d..b07338d500a 100644
--- a/app/services/create_branch_service.rb
+++ b/app/services/create_branch_service.rb
@@ -1,5 +1,7 @@
class CreateBranchService < BaseService
def execute(branch_name, ref)
+ create_master_branch if project.empty_repo?
+
result = ValidateNewBranchService.new(project, current_user)
.execute(branch_name)
@@ -19,4 +21,16 @@ class CreateBranchService < BaseService
def success(branch)
super().merge(branch: branch)
end
+
+ private
+
+ def create_master_branch
+ project.repository.commit_file(
+ current_user,
+ '/README.md',
+ '',
+ message: 'Add README.md',
+ branch_name: 'master',
+ update: false)
+ end
end
diff --git a/app/services/create_snippet_service.rb b/app/services/create_snippet_service.rb
index 14f5ba064ff..40286dbf3bf 100644
--- a/app/services/create_snippet_service.rb
+++ b/app/services/create_snippet_service.rb
@@ -1,7 +1,8 @@
class CreateSnippetService < BaseService
+ include SpamCheckService
+
def execute
- request = params.delete(:request)
- api = params.delete(:api)
+ filter_spam_check_params
snippet = if project
project.snippets.build(params)
@@ -15,10 +16,11 @@ class CreateSnippetService < BaseService
end
snippet.author = current_user
- snippet.spam = SpamService.new(snippet, request).check(api)
+
+ spam_check(snippet, current_user)
if snippet.save
- UserAgentDetailService.new(snippet, request).create
+ UserAgentDetailService.new(snippet, @request).create
end
snippet
diff --git a/app/services/create_tag_service.rb b/app/services/create_tag_service.rb
deleted file mode 100644
index fe9353afeb8..00000000000
--- a/app/services/create_tag_service.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-class CreateTagService < BaseService
- def execute(tag_name, target, message, release_description = nil)
- valid_tag = Gitlab::GitRefValidator.validate(tag_name)
- return error('Tag name invalid') unless valid_tag
-
- repository = project.repository
- message.strip! if message
-
- new_tag = nil
-
- begin
- new_tag = repository.add_tag(current_user, tag_name, target, message)
- rescue Rugged::TagError
- return error("Tag #{tag_name} already exists")
- rescue GitHooksService::PreReceiveError => ex
- return error(ex.message)
- end
-
- if new_tag
- if release_description
- CreateReleaseService.new(@project, @current_user).
- execute(tag_name, release_description)
- end
-
- success.merge(tag: new_tag)
- else
- error("Target #{target} is invalid")
- end
- end
-end
diff --git a/app/services/delete_tag_service.rb b/app/services/delete_tag_service.rb
deleted file mode 100644
index 9d4bffb93e9..00000000000
--- a/app/services/delete_tag_service.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-class DeleteTagService < BaseService
- def execute(tag_name)
- repository = project.repository
- tag = repository.find_tag(tag_name)
-
- unless tag
- return error('No such tag', 404)
- end
-
- if repository.rm_tag(current_user, tag_name)
- release = project.releases.find_by(tag: tag_name)
- release.destroy if release
-
- push_data = build_push_data(tag)
- EventCreateService.new.push(project, current_user, push_data)
- project.execute_hooks(push_data.dup, :tag_push_hooks)
- project.execute_services(push_data.dup, :tag_push_hooks)
-
- success('Tag was removed')
- else
- error('Failed to remove tag')
- end
- end
-
- def error(message, return_code = 400)
- super(message).merge(return_code: return_code)
- end
-
- def success(message)
- super().merge(message: message)
- end
-
- def build_push_data(tag)
- Gitlab::DataBuilder::Push.build(
- project,
- current_user,
- tag.dereferenced_target.sha,
- Gitlab::Git::BLANK_SHA,
- "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}",
- [])
- end
-end
diff --git a/app/services/delete_user_service.rb b/app/services/delete_user_service.rb
deleted file mode 100644
index eaff88d6463..00000000000
--- a/app/services/delete_user_service.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-class DeleteUserService
- attr_accessor :current_user
-
- def initialize(current_user)
- @current_user = current_user
- end
-
- def execute(user, options = {})
- if !options[:delete_solo_owned_groups] && user.solo_owned_groups.present?
- user.errors[:base] << 'You must transfer ownership or delete groups before you can remove user'
- return user
- end
-
- user.solo_owned_groups.each do |group|
- DestroyGroupService.new(group, current_user).execute
- end
-
- user.personal_projects.each do |project|
- # Skip repository removal because we remove directory with namespace
- # that contain all this repositories
- ::Projects::DestroyService.new(project, current_user, skip_repo: true).async_execute
- end
-
- # Destroy the namespace after destroying the user since certain methods may depend on the namespace existing
- namespace = user.namespace
- user_data = user.destroy
- namespace.really_destroy!
-
- user_data
- end
-end
diff --git a/app/services/destroy_group_service.rb b/app/services/destroy_group_service.rb
deleted file mode 100644
index 2316c57bf1e..00000000000
--- a/app/services/destroy_group_service.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-class DestroyGroupService
- attr_accessor :group, :current_user
-
- def initialize(group, user)
- @group, @current_user = group, user
- end
-
- def async_execute
- # Soft delete via paranoia gem
- group.destroy
- job_id = GroupDestroyWorker.perform_async(group.id, current_user.id)
- Rails.logger.info("User #{current_user.id} scheduled a deletion of group ID #{group.id} with job ID #{job_id}")
- end
-
- def execute
- group.projects.each do |project|
- # Execute the destruction of the models immediately to ensure atomic cleanup.
- # Skip repository removal because we remove directory with namespace
- # that contain all these repositories
- ::Projects::DestroyService.new(project, current_user, skip_repo: true).execute
- end
-
- group.children.each do |group|
- DestroyGroupService.new(group, current_user).async_execute
- end
-
- group.really_destroy!
- end
-end
diff --git a/app/services/files/delete_service.rb b/app/services/files/destroy_service.rb
index 50f0ffcac9f..c3be806a42d 100644
--- a/app/services/files/delete_service.rb
+++ b/app/services/files/destroy_service.rb
@@ -1,5 +1,5 @@
module Files
- class DeleteService < Files::BaseService
+ class DestroyService < Files::BaseService
def commit
repository.remove_file(
current_user,
diff --git a/app/services/files/multi_service.rb b/app/services/files/multi_service.rb
index 6ba868df04d..af6da5b9d56 100644
--- a/app/services/files/multi_service.rb
+++ b/app/services/files/multi_service.rb
@@ -55,7 +55,7 @@ module Files
file_path = action[:file_path]
file_path = action[:previous_path] if action[:action] == :move
- blob = repository.blob_at_branch(params[:branch_name], file_path)
+ blob = repository.blob_at_branch(params[:branch], file_path)
unless blob
raise_error("File to be #{action[:action]}d `#{file_path}` does not exist.")
@@ -89,7 +89,7 @@ module Files
def validate_create(action)
return if project.empty_repo?
- if repository.blob_at_branch(params[:branch_name], action[:file_path])
+ if repository.blob_at_branch(params[:branch], action[:file_path])
raise_error("Your changes could not be committed because a file with the name `#{action[:file_path]}` already exists.")
end
end
@@ -102,14 +102,14 @@ module Files
raise_error("You must supply the original file path when moving file `#{action[:file_path]}`.")
end
- blob = repository.blob_at_branch(params[:branch_name], action[:file_path])
+ blob = repository.blob_at_branch(params[:branch], action[:file_path])
if blob
raise_error("Move destination `#{action[:file_path]}` already exists.")
end
if action[:content].nil?
- blob = repository.blob_at_branch(params[:branch_name], action[:previous_path])
+ blob = repository.blob_at_branch(params[:branch], action[:previous_path])
blob.load_all_data!(repository) if blob.truncated?
params[:actions][index][:content] = blob.data
end
diff --git a/app/services/groups/destroy_service.rb b/app/services/groups/destroy_service.rb
new file mode 100644
index 00000000000..2e2d7f884ac
--- /dev/null
+++ b/app/services/groups/destroy_service.rb
@@ -0,0 +1,27 @@
+module Groups
+ class DestroyService < Groups::BaseService
+ def async_execute
+ # Soft delete via paranoia gem
+ group.destroy
+ job_id = GroupDestroyWorker.perform_async(group.id, current_user.id)
+ Rails.logger.info("User #{current_user.id} scheduled a deletion of group ID #{group.id} with job ID #{job_id}")
+ end
+
+ def execute
+ group.prepare_for_destroy
+
+ group.projects.with_deleted.each do |project|
+ # Execute the destruction of the models immediately to ensure atomic cleanup.
+ # Skip repository removal because we remove directory with namespace
+ # that contain all these repositories
+ ::Projects::DestroyService.new(project, current_user, skip_repo: true).execute
+ end
+
+ group.children.each do |group|
+ DestroyService.new(group, current_user).async_execute
+ end
+
+ group.really_destroy!
+ end
+ end
+end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 5f3ced49665..9500faf2862 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -191,14 +191,12 @@ class IssuableBaseService < BaseService
# To be overridden by subclasses
end
- def after_update(issuable)
+ def before_update(issuable)
# To be overridden by subclasses
end
- def update_issuable(issuable, attributes)
- issuable.with_transaction_returning_status do
- issuable.update(attributes.merge(updated_by: current_user))
- end
+ def after_update(issuable)
+ # To be overridden by subclasses
end
def update(issuable)
@@ -212,16 +210,22 @@ class IssuableBaseService < BaseService
label_ids = process_label_ids(params, existing_label_ids: issuable.label_ids)
params[:label_ids] = label_ids if labels_changing?(issuable.label_ids, label_ids)
- if params.present? && update_issuable(issuable, params)
- # We do not touch as it will affect a update on updated_at field
- ActiveRecord::Base.no_touching do
- handle_common_system_notes(issuable, old_labels: old_labels)
- end
+ if params.present?
+ issuable.assign_attributes(params.merge(updated_by: current_user))
+
+ before_update(issuable)
- handle_changes(issuable, old_labels: old_labels, old_mentioned_users: old_mentioned_users)
- after_update(issuable)
- issuable.create_new_cross_references!(current_user)
- execute_hooks(issuable, 'update')
+ if issuable.with_transaction_returning_status { issuable.save }
+ # We do not touch as it will affect a update on updated_at field
+ ActiveRecord::Base.no_touching do
+ handle_common_system_notes(issuable, old_labels: old_labels)
+ end
+
+ handle_changes(issuable, old_labels: old_labels, old_mentioned_users: old_mentioned_users)
+ after_update(issuable)
+ issuable.create_new_cross_references!(current_user)
+ execute_hooks(issuable, 'update')
+ end
end
issuable
diff --git a/app/services/issues/build_service.rb b/app/services/issues/build_service.rb
index a63982f60c8..7cd927d8005 100644
--- a/app/services/issues/build_service.rb
+++ b/app/services/issues/build_service.rb
@@ -44,7 +44,15 @@ module Issues
end
def issue_params
- @issue_params ||= issue_params_with_info_from_merge_request.merge(params.slice(:title, :description))
+ @issue_params ||= issue_params_with_info_from_merge_request.merge(whitelisted_issue_params)
+ end
+
+ def whitelisted_issue_params
+ if can?(current_user, :admin_issue, project)
+ params.slice(:title, :description, :milestone_id)
+ else
+ params.slice(:title, :description)
+ end
end
end
end
diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb
index c9168f74249..366b3572738 100644
--- a/app/services/issues/create_service.rb
+++ b/app/services/issues/create_service.rb
@@ -1,10 +1,9 @@
module Issues
class CreateService < Issues::BaseService
+ include SpamCheckService
+
def execute
- @request = params.delete(:request)
- @api = params.delete(:api)
- @recaptcha_verified = params.delete(:recaptcha_verified)
- @spam_log_id = params.delete(:spam_log_id)
+ filter_spam_check_params
issue_attributes = params.merge(merge_request_for_resolving_discussions: merge_request_for_resolving_discussions)
@issue = BuildService.new(project, current_user, issue_attributes).execute
@@ -12,14 +11,8 @@ module Issues
create(@issue)
end
- def before_create(issuable)
- if @recaptcha_verified
- spam_log = current_user.spam_logs.find_by(id: @spam_log_id, title: issuable.title)
- spam_log.update!(recaptcha_verified: true) if spam_log
- else
- issuable.spam = spam_service.check(@api)
- issuable.spam_log = spam_service.spam_log
- end
+ def before_create(issue)
+ spam_check(issue, current_user)
end
def after_create(issuable)
@@ -42,10 +35,6 @@ module Issues
private
- def spam_service
- @spam_service ||= SpamService.new(@issue, @request)
- end
-
def user_agent_detail_service
UserAgentDetailService.new(@issue, @request)
end
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index 78cbf94ec69..22e32b13259 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -1,9 +1,17 @@
module Issues
class UpdateService < Issues::BaseService
+ include SpamCheckService
+
def execute(issue)
+ filter_spam_check_params
+
update(issue)
end
+ def before_update(issue)
+ spam_check(issue, current_user)
+ end
+
def handle_changes(issue, old_labels: [], old_mentioned_users: [])
if has_changes?(issue, old_labels: old_labels)
todo_service.mark_pending_todos_as_done(issue, current_user)
diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb
index 431da8372c9..2e089149ca8 100644
--- a/app/services/members/destroy_service.rb
+++ b/app/services/members/destroy_service.rb
@@ -4,7 +4,7 @@ module Members
attr_accessor :source
- ALLOWED_SCOPES = %i[members requesters all]
+ ALLOWED_SCOPES = %i[members requesters all].freeze
def initialize(source, current_user, params = {})
@source = source
diff --git a/app/services/merge_requests/add_todo_when_build_fails_service.rb b/app/services/merge_requests/add_todo_when_build_fails_service.rb
index 12a8415d9a5..727768b1a39 100644
--- a/app/services/merge_requests/add_todo_when_build_fails_service.rb
+++ b/app/services/merge_requests/add_todo_when_build_fails_service.rb
@@ -18,5 +18,11 @@ module MergeRequests
todo_service.merge_request_build_retried(merge_request)
end
end
+
+ def close_all(pipeline)
+ pipeline_merge_requests(pipeline) do |merge_request|
+ todo_service.merge_request_build_retried(merge_request)
+ end
+ end
end
end
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index f4d52e3ebbd..9d4739e37bb 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -2,18 +2,14 @@ module MergeRequests
class BuildService < MergeRequests::BaseService
def execute
self.merge_request = MergeRequest.new(params)
- merge_request.can_be_created = true
merge_request.compare_commits = []
merge_request.source_project = find_source_project
merge_request.target_project = find_target_project
merge_request.target_branch = find_target_branch
+ merge_request.can_be_created = branches_valid? && source_branch_specified? && target_branch_specified?
- if branches_specified? && branches_valid?
- compare_branches
- assign_title_and_description
- else
- merge_request.can_be_created = false
- end
+ compare_branches if branches_present?
+ assign_title_and_description if merge_request.can_be_created
merge_request
end
@@ -37,11 +33,17 @@ module MergeRequests
target_branch || target_project.default_branch
end
- def branches_specified?
- params[:source_branch] && params[:target_branch]
+ def source_branch_specified?
+ params[:source_branch].present?
+ end
+
+ def target_branch_specified?
+ params[:target_branch].present?
end
def branches_valid?
+ return false unless source_branch_specified? || target_branch_specified?
+
validate_branches
errors.blank?
end
@@ -55,8 +57,10 @@ module MergeRequests
target_branch
)
- merge_request.compare_commits = compare.commits
- merge_request.compare = compare
+ if compare
+ merge_request.compare_commits = compare.commits
+ merge_request.compare = compare
+ end
end
def validate_branches
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index 5ca6fec962d..3da1b657a41 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -11,18 +11,20 @@ module MergeRequests
def execute(merge_request)
@merge_request = merge_request
- return log_merge_error('Merge request is not mergeable', true) unless @merge_request.mergeable?
+ unless @merge_request.mergeable?
+ return log_merge_error('Merge request is not mergeable', save_message_on_model: true)
+ end
@source = find_merge_source
- return log_merge_error('No source for merge', true) unless @source
+ unless @source
+ return log_merge_error('No source for merge', save_message_on_model: true)
+ end
merge_request.in_locked_state do
if commit
after_merge
success
- else
- log_merge_error('Can not merge changes', true)
end
end
end
@@ -43,11 +45,11 @@ module MergeRequests
if commit_id
merge_request.update(merge_commit_sha: commit_id)
else
- merge_request.update(merge_error: 'Conflicts detected during merge')
+ log_merge_error('Conflicts detected during merge', save_message_on_model: true)
false
end
rescue GitHooksService::PreReceiveError => e
- merge_request.update(merge_error: e.message)
+ log_merge_error(e.message, save_message_on_model: true)
false
rescue StandardError => e
merge_request.update(merge_error: "Something went wrong during merge: #{e.message}")
@@ -70,10 +72,10 @@ module MergeRequests
@merge_request.force_remove_source_branch? ? @merge_request.author : current_user
end
- def log_merge_error(message, http_error = false)
+ def log_merge_error(message, save_message_on_model: false)
Rails.logger.error("MergeService ERROR: #{merge_request_info} - #{message}")
- error(message) if http_error
+ @merge_request.update(merge_error: message) if save_message_on_model
end
def merge_request_info
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index b4bfb0e5e8c..581d18032e6 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -144,7 +144,11 @@ module MergeRequests
return unless @commits.present?
merge_requests_for_source_branch.each do |merge_request|
- wip_commit = @commits.detect(&:work_in_progress?)
+ commit_shas = merge_request.commits_sha
+
+ wip_commit = @commits.detect do |commit|
+ commit.work_in_progress? && commit_shas.include?(commit.sha)
+ end
if wip_commit && !merge_request.work_in_progress?
merge_request.update(title: merge_request.wip_title)
diff --git a/app/services/notes/delete_service.rb b/app/services/notes/destroy_service.rb
index a673e8e9dde..b819bd17039 100644
--- a/app/services/notes/delete_service.rb
+++ b/app/services/notes/destroy_service.rb
@@ -1,5 +1,5 @@
module Notes
- class DeleteService < BaseService
+ class DestroyService < BaseService
def execute(note)
note.destroy
end
diff --git a/app/services/notes/slash_commands_service.rb b/app/services/notes/slash_commands_service.rb
index 56913568cae..ad1e6f6774a 100644
--- a/app/services/notes/slash_commands_service.rb
+++ b/app/services/notes/slash_commands_service.rb
@@ -3,7 +3,7 @@ module Notes
UPDATE_SERVICES = {
'Issue' => Issues::UpdateService,
'MergeRequest' => MergeRequests::UpdateService
- }
+ }.freeze
def self.noteable_update_service(note)
UPDATE_SERVICES[note.noteable_type]
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index b2cc39763f3..3734e3c4253 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -217,7 +217,7 @@ class NotificationService
recipients = reject_unsubscribed_users(recipients, note.noteable)
recipients = reject_users_without_access(recipients, note.noteable)
- recipients.delete(note.author)
+ recipients.delete(note.author) unless note.author.notified_of_own_activity?
recipients = recipients.uniq
notify_method = "note_#{note.to_ability_name}_email".to_sym
@@ -327,8 +327,9 @@ class NotificationService
recipients ||= build_recipients(
pipeline,
pipeline.project,
- nil, # The acting user, who won't be added to recipients
- action: pipeline.status).map(&:notification_email)
+ pipeline.user,
+ action: pipeline.status,
+ skip_current_user: false).map(&:notification_email)
if recipients.any?
mailer.public_send(email_template, pipeline, recipients).deliver_later
@@ -627,7 +628,7 @@ class NotificationService
recipients = reject_unsubscribed_users(recipients, target)
recipients = reject_users_without_access(recipients, target)
- recipients.delete(current_user) if skip_current_user
+ recipients.delete(current_user) if skip_current_user && !current_user.notified_of_own_activity?
recipients.uniq
end
@@ -636,7 +637,7 @@ class NotificationService
recipients = add_labels_subscribers([], project, target, labels: labels)
recipients = reject_unsubscribed_users(recipients, target)
recipients = reject_users_without_access(recipients, target)
- recipients.delete(current_user)
+ recipients.delete(current_user) unless current_user.notified_of_own_activity?
recipients.uniq
end
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index c7cce0c55b9..6dc3d8c2416 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -97,7 +97,7 @@ module Projects
@project.team << [current_user, :master, current_user]
end
- @project.group.refresh_members_authorized_projects if @project.group
+ @project.group&.refresh_members_authorized_projects
end
def skip_wiki?
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index a08c6fcd94b..2e06826c311 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -4,7 +4,7 @@ module Projects
class DestroyError < StandardError; end
- DELETED_FLAG = '+deleted'
+ DELETED_FLAG = '+deleted'.freeze
def async_execute
project.transaction do
@@ -17,8 +17,6 @@ module Projects
def execute
return false unless can?(current_user, :remove_project, project)
- project.team.truncate
-
repo_path = project.path_with_namespace
wiki_path = repo_path + '.wiki'
@@ -30,6 +28,7 @@ module Projects
Projects::UnlinkForkService.new(project, current_user).execute
Project.transaction do
+ project.team.truncate
project.destroy!
unless remove_registry_tags
diff --git a/app/services/projects/download_service.rb b/app/services/projects/download_service.rb
index f06a3d44c17..604747e39d0 100644
--- a/app/services/projects/download_service.rb
+++ b/app/services/projects/download_service.rb
@@ -2,7 +2,7 @@ module Projects
class DownloadService < BaseService
WHITELIST = [
/^[^.]+\.fogbugz.com$/
- ]
+ ].freeze
def initialize(project, url)
@project, @url = project, url
@@ -25,7 +25,7 @@ module Projects
end
def http?(url)
- url =~ /\A#{URI::regexp(['http', 'https'])}\z/
+ url =~ /\A#{URI.regexp(%w(http https))}\z/
end
def valid_domain?(url)
diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb
index 96c363c8d1a..e6193fcacee 100644
--- a/app/services/projects/participants_service.rb
+++ b/app/services/projects/participants_service.rb
@@ -36,7 +36,7 @@ module Projects
def groups
current_user.authorized_groups.sort_by(&:path).map do |group|
count = group.users.count
- { username: group.path, name: group.name, count: count, avatar_url: group.avatar_url }
+ { username: group.full_path, name: group.full_name, count: count, avatar_url: group.avatar_url }
end
end
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index 484700c8c29..20dfbddc823 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -30,7 +30,7 @@ module Projects
Project.transaction do
old_path = project.path_with_namespace
old_group = project.group
- new_path = File.join(new_namespace.try(:path) || '', project.path)
+ new_path = File.join(new_namespace.try(:full_path) || '', project.path)
if Project.where(path: project.path, namespace_id: new_namespace.try(:id)).present?
raise TransferError.new("Project with same path in target namespace already exists")
@@ -63,10 +63,10 @@ module Projects
Labels::TransferService.new(current_user, old_group, project).execute
# Move uploads
- Gitlab::UploadsTransfer.new.move_project(project.path, old_namespace.path, new_namespace.path)
+ Gitlab::UploadsTransfer.new.move_project(project.path, old_namespace.full_path, new_namespace.full_path)
# Move pages
- Gitlab::PagesTransfer.new.move_project(project.path, old_namespace.path, new_namespace.path)
+ Gitlab::PagesTransfer.new.move_project(project.path, old_namespace.full_path, new_namespace.full_path)
project.old_path_with_namespace = old_path
diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb
index f5f9ee88912..2d42c4fc04a 100644
--- a/app/services/projects/update_pages_service.rb
+++ b/app/services/projects/update_pages_service.rb
@@ -2,7 +2,7 @@ module Projects
class UpdatePagesService < BaseService
BLOCK_SIZE = 32.kilobytes
MAX_SIZE = 1.terabyte
- SITE_PATH = 'public/'
+ SITE_PATH = 'public/'.freeze
attr_reader :build
diff --git a/app/services/projects/upload_service.rb b/app/services/projects/upload_service.rb
index 012e82a7704..be34d4fa9b8 100644
--- a/app/services/projects/upload_service.rb
+++ b/app/services/projects/upload_service.rb
@@ -5,7 +5,7 @@ module Projects
end
def execute
- return nil unless @file and @file.size <= max_attachment_size
+ return nil unless @file && @file.size <= max_attachment_size
uploader = FileUploader.new(@project)
uploader.store!(@file)
diff --git a/app/services/protected_branches/api_update_service.rb b/app/services/protected_branches/api_update_service.rb
index 050cb3b738b..bdb0e0cc8bf 100644
--- a/app/services/protected_branches/api_update_service.rb
+++ b/app/services/protected_branches/api_update_service.rb
@@ -15,16 +15,16 @@ module ProtectedBranches
case @developers_can_push
when true
- params.merge!(push_access_levels_attributes: [{ access_level: Gitlab::Access::DEVELOPER }])
+ params[:push_access_levels_attributes] = [{ access_level: Gitlab::Access::DEVELOPER }]
when false
- params.merge!(push_access_levels_attributes: [{ access_level: Gitlab::Access::MASTER }])
+ params[:push_access_levels_attributes] = [{ access_level: Gitlab::Access::MASTER }]
end
case @developers_can_merge
when true
- params.merge!(merge_access_levels_attributes: [{ access_level: Gitlab::Access::DEVELOPER }])
+ params[:merge_access_levels_attributes] = [{ access_level: Gitlab::Access::DEVELOPER }]
when false
- params.merge!(merge_access_levels_attributes: [{ access_level: Gitlab::Access::MASTER }])
+ params[:merge_access_levels_attributes] = [{ access_level: Gitlab::Access::MASTER }]
end
service = ProtectedBranches::UpdateService.new(@project, @current_user, @params)
diff --git a/app/services/spam_check_service.rb b/app/services/spam_check_service.rb
new file mode 100644
index 00000000000..023e0824e85
--- /dev/null
+++ b/app/services/spam_check_service.rb
@@ -0,0 +1,24 @@
+# SpamCheckService
+#
+# Provide helper methods for checking if a given spammable object has
+# potential spam data.
+#
+# Dependencies:
+# - params with :request
+#
+module SpamCheckService
+ def filter_spam_check_params
+ @request = params.delete(:request)
+ @api = params.delete(:api)
+ @recaptcha_verified = params.delete(:recaptcha_verified)
+ @spam_log_id = params.delete(:spam_log_id)
+ end
+
+ def spam_check(spammable, user)
+ spam_service = SpamService.new(spammable, @request)
+
+ spam_service.when_recaptcha_verified(@recaptcha_verified, @api) do
+ user.spam_logs.find_by(id: @spam_log_id)&.update!(recaptcha_verified: true)
+ end
+ end
+end
diff --git a/app/services/spam_service.rb b/app/services/spam_service.rb
index 024a7c19d33..3e65b7d31a3 100644
--- a/app/services/spam_service.rb
+++ b/app/services/spam_service.rb
@@ -17,15 +17,6 @@ class SpamService
end
end
- def check(api = false)
- return false unless request && check_for_spam?
-
- return false unless akismet.is_spam?
-
- create_spam_log(api)
- true
- end
-
def mark_as_spam!
return false unless spammable.submittable_as_spam?
@@ -36,8 +27,30 @@ class SpamService
end
end
+ def when_recaptcha_verified(recaptcha_verified, api = false)
+ # In case it's a request which is already verified through recaptcha, yield
+ # block.
+ if recaptcha_verified
+ yield
+ else
+ # Otherwise, it goes to Akismet and check if it's a spam. If that's the
+ # case, it assigns spammable record as "spam" and create a SpamLog record.
+ spammable.spam = check(api)
+ spammable.spam_log = spam_log
+ end
+ end
+
private
+ def check(api)
+ return false unless request && check_for_spam?
+
+ return false unless akismet.is_spam?
+
+ create_spam_log(api)
+ true
+ end
+
def akismet
@akismet ||= AkismetService.new(
spammable_owner,
diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb
index a2bfa422c9d..9b6dd013e3a 100644
--- a/app/services/system_hooks_service.rb
+++ b/app/services/system_hooks_service.rb
@@ -33,9 +33,7 @@ class SystemHooksService
data.merge!(project_data(model))
if event == :rename || event == :transfer
- data.merge!({
- old_path_with_namespace: model.old_path_with_namespace
- })
+ data[:old_path_with_namespace] = model.old_path_with_namespace
end
data
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 87ba72cf991..55b548a12f9 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -356,10 +356,10 @@ module SystemNoteService
note: cross_reference_note_content(gfm_reference)
}
- if noteable.kind_of?(Commit)
+ if noteable.is_a?(Commit)
note_options.merge!(noteable_type: 'Commit', commit_id: noteable.id)
else
- note_options.merge!(noteable: noteable)
+ note_options[:noteable] = noteable
end
if noteable.is_a?(ExternalIssue)
@@ -408,12 +408,13 @@ module SystemNoteService
# Initial scope should be system notes of this noteable type
notes = Note.system.where(noteable_type: noteable.class)
- if noteable.is_a?(Commit)
- # Commits have non-integer IDs, so they're stored in `commit_id`
- notes = notes.where(commit_id: noteable.id)
- else
- notes = notes.where(noteable_id: noteable.id)
- end
+ notes =
+ if noteable.is_a?(Commit)
+ # Commits have non-integer IDs, so they're stored in `commit_id`
+ notes.where(commit_id: noteable.id)
+ else
+ notes.where(noteable_id: noteable.id)
+ end
notes_for_mentioner(mentioner, noteable, notes).exists?
end
diff --git a/app/services/tags/create_service.rb b/app/services/tags/create_service.rb
new file mode 100644
index 00000000000..1756da9e519
--- /dev/null
+++ b/app/services/tags/create_service.rb
@@ -0,0 +1,32 @@
+module Tags
+ class CreateService < BaseService
+ def execute(tag_name, target, message, release_description = nil)
+ valid_tag = Gitlab::GitRefValidator.validate(tag_name)
+ return error('Tag name invalid') unless valid_tag
+
+ repository = project.repository
+ message&.strip!
+
+ new_tag = nil
+
+ begin
+ new_tag = repository.add_tag(current_user, tag_name, target, message)
+ rescue Rugged::TagError
+ return error("Tag #{tag_name} already exists")
+ rescue GitHooksService::PreReceiveError => ex
+ return error(ex.message)
+ end
+
+ if new_tag
+ if release_description
+ CreateReleaseService.new(@project, @current_user).
+ execute(tag_name, release_description)
+ end
+
+ success.merge(tag: new_tag)
+ else
+ error("Target #{target} is invalid")
+ end
+ end
+ end
+end
diff --git a/app/services/tags/destroy_service.rb b/app/services/tags/destroy_service.rb
new file mode 100644
index 00000000000..910b4f5e361
--- /dev/null
+++ b/app/services/tags/destroy_service.rb
@@ -0,0 +1,44 @@
+module Tags
+ class DestroyService < BaseService
+ def execute(tag_name)
+ repository = project.repository
+ tag = repository.find_tag(tag_name)
+
+ unless tag
+ return error('No such tag', 404)
+ end
+
+ if repository.rm_tag(current_user, tag_name)
+ release = project.releases.find_by(tag: tag_name)
+ release&.destroy
+
+ push_data = build_push_data(tag)
+ EventCreateService.new.push(project, current_user, push_data)
+ project.execute_hooks(push_data.dup, :tag_push_hooks)
+ project.execute_services(push_data.dup, :tag_push_hooks)
+
+ success('Tag was removed')
+ else
+ error('Failed to remove tag')
+ end
+ end
+
+ def error(message, return_code = 400)
+ super(message).merge(return_code: return_code)
+ end
+
+ def success(message)
+ super().merge(message: message)
+ end
+
+ def build_push_data(tag)
+ Gitlab::DataBuilder::Push.build(
+ project,
+ current_user,
+ tag.dereferenced_target.sha,
+ Gitlab::Git::BLANK_SHA,
+ "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}",
+ [])
+ end
+ end
+end
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index 1bd6ce416ab..ad86b4f9f42 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -170,16 +170,20 @@ class TodoService
# When user marks some todos as done
def mark_todos_as_done(todos, current_user)
- mark_todos_as_done_by_ids(todos.select(&:id), current_user)
+ update_todos_state_by_ids(todos.select(&:id), current_user, :done)
end
def mark_todos_as_done_by_ids(ids, current_user)
- todos = current_user.todos.where(id: ids)
+ update_todos_state_by_ids(ids, current_user, :done)
+ end
- # Only return those that are not really on that state
- marked_todos = todos.where.not(state: :done).update_all(state: :done)
- current_user.update_todos_count_cache
- marked_todos
+ # When user marks some todos as pending
+ def mark_todos_as_pending(todos, current_user)
+ update_todos_state_by_ids(todos.select(&:id), current_user, :pending)
+ end
+
+ def mark_todos_as_pending_by_ids(ids, current_user)
+ update_todos_state_by_ids(ids, current_user, :pending)
end
# When user marks an issue as todo
@@ -194,6 +198,15 @@ class TodoService
private
+ def update_todos_state_by_ids(ids, current_user, state)
+ todos = current_user.todos.where(id: ids)
+
+ # Only return those that are not really on that state
+ marked_todos = todos.where.not(state: state).update_all(state: state)
+ current_user.update_todos_count_cache
+ marked_todos
+ end
+
def create_todos(users, attributes)
Array(users).map do |user|
next if pending_todos(user, attributes).exists?
@@ -243,6 +256,12 @@ class TodoService
end
def create_mention_todos(project, target, author, note = nil)
+ # Create Todos for directly addressed users
+ directly_addressed_users = filter_directly_addressed_users(project, note || target, author)
+ attributes = attributes_for_todo(project, target, author, Todo::DIRECTLY_ADDRESSED, note)
+ create_todos(directly_addressed_users, attributes)
+
+ # Create Todos for mentioned users
mentioned_users = filter_mentioned_users(project, note || target, author)
attributes = attributes_for_todo(project, target, author, Todo::MENTIONED, note)
create_todos(mentioned_users, attributes)
@@ -282,10 +301,18 @@ class TodoService
)
end
+ def filter_todo_users(users, project, target)
+ reject_users_without_access(users, project, target).uniq
+ end
+
def filter_mentioned_users(project, target, author)
mentioned_users = target.mentioned_users(author)
- mentioned_users = reject_users_without_access(mentioned_users, project, target)
- mentioned_users.uniq
+ filter_todo_users(mentioned_users, project, target)
+ end
+
+ def filter_directly_addressed_users(project, target, author)
+ directly_addressed_users = target.directly_addressed_users(author)
+ filter_todo_users(directly_addressed_users, project, target)
end
def reject_users_without_access(users, project, target)
diff --git a/app/services/update_snippet_service.rb b/app/services/update_snippet_service.rb
index a6bb36821c3..358bca73aec 100644
--- a/app/services/update_snippet_service.rb
+++ b/app/services/update_snippet_service.rb
@@ -1,4 +1,6 @@
class UpdateSnippetService < BaseService
+ include SpamCheckService
+
attr_accessor :snippet
def initialize(project, user, snippet, params)
@@ -9,7 +11,7 @@ class UpdateSnippetService < BaseService
def execute
# check that user is allowed to set specified visibility_level
new_visibility = params[:visibility_level]
-
+
if new_visibility && new_visibility.to_i != snippet.visibility_level
unless Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
deny_visibility_level(snippet, new_visibility)
@@ -17,6 +19,10 @@ class UpdateSnippetService < BaseService
end
end
- snippet.update_attributes(params)
+ filter_spam_check_params
+ snippet.assign_attributes(params)
+ spam_check(snippet, current_user)
+
+ snippet.save
end
end
diff --git a/app/services/users/destroy_service.rb b/app/services/users/destroy_service.rb
new file mode 100644
index 00000000000..833da5bc5d1
--- /dev/null
+++ b/app/services/users/destroy_service.rb
@@ -0,0 +1,56 @@
+module Users
+ class DestroyService
+ attr_accessor :current_user
+
+ def initialize(current_user)
+ @current_user = current_user
+ end
+
+ def execute(user, options = {})
+ unless Ability.allowed?(current_user, :destroy_user, user)
+ raise Gitlab::Access::AccessDeniedError, "#{current_user} tried to destroy user #{user}!"
+ end
+
+ if !options[:delete_solo_owned_groups] && user.solo_owned_groups.present?
+ user.errors[:base] << 'You must transfer ownership or delete groups before you can remove user'
+ return user
+ end
+
+ user.solo_owned_groups.each do |group|
+ Groups::DestroyService.new(group, current_user).execute
+ end
+
+ user.personal_projects.each do |project|
+ # Skip repository removal because we remove directory with namespace
+ # that contain all this repositories
+ ::Projects::DestroyService.new(project, current_user, skip_repo: true).async_execute
+ end
+
+ move_issues_to_ghost_user(user)
+
+ # Destroy the namespace after destroying the user since certain methods may depend on the namespace existing
+ namespace = user.namespace
+ user_data = user.destroy
+ namespace.really_destroy!
+
+ user_data
+ end
+
+ private
+
+ def move_issues_to_ghost_user(user)
+ # Block the user before moving issues to prevent a data race.
+ # If the user creates an issue after `move_issues_to_ghost_user`
+ # runs and before the user is destroyed, the destroy will fail with
+ # an exception. We block the user so that issues can't be created
+ # after `move_issues_to_ghost_user` runs and before the destroy happens.
+ user.block
+
+ ghost_user = User.ghost
+
+ user.issues.update_all(author_id: ghost_user.id)
+
+ user.reload
+ end
+ end
+end
diff --git a/app/services/wiki_pages/destroy_service.rb b/app/services/wiki_pages/destroy_service.rb
new file mode 100644
index 00000000000..6b93fb2f6d7
--- /dev/null
+++ b/app/services/wiki_pages/destroy_service.rb
@@ -0,0 +1,11 @@
+module WikiPages
+ class DestroyService < WikiPages::BaseService
+ def execute(page)
+ if page&.delete
+ execute_hooks(page, 'delete')
+ end
+
+ page
+ end
+ end
+end
diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb
index 47bef7cd1e4..23b7318827c 100644
--- a/app/uploaders/file_uploader.rb
+++ b/app/uploaders/file_uploader.rb
@@ -36,7 +36,7 @@ class FileUploader < GitlabUploader
escaped_filename = filename.gsub("]", "\\]")
markdown = "[#{escaped_filename}](#{self.secure_url})"
- markdown.prepend("!") if image_or_video?
+ markdown.prepend("!") if image_or_video? || dangerous?
{
alt: filename,
diff --git a/app/uploaders/uploader_helper.rb b/app/uploaders/uploader_helper.rb
index fbaea2744a3..bee311583ea 100644
--- a/app/uploaders/uploader_helper.rb
+++ b/app/uploaders/uploader_helper.rb
@@ -1,12 +1,15 @@
# Extra methods for uploader
module UploaderHelper
- IMAGE_EXT = %w[png jpg jpeg gif bmp tiff svg]
+ IMAGE_EXT = %w[png jpg jpeg gif bmp tiff].freeze
# We recommend using the .mp4 format over .mov. Videos in .mov format can
# still be used but you really need to make sure they are served with the
# proper MIME type video/mp4 and not video/quicktime or your videos won't play
# on IE >= 9.
# http://archive.sublimevideo.info/20150912/docs.sublimevideo.net/troubleshooting.html
- VIDEO_EXT = %w[mp4 m4v mov webm ogv]
+ VIDEO_EXT = %w[mp4 m4v mov webm ogv].freeze
+ # These extension types can contain dangerous code and should only be embedded inline with
+ # proper filtering. They should always be tagged as "Content-Disposition: attachment", not "inline".
+ DANGEROUS_EXT = %w[svg].freeze
def image?
extension_match?(IMAGE_EXT)
@@ -20,6 +23,10 @@ module UploaderHelper
image? || video?
end
+ def dangerous?
+ extension_match?(DANGEROUS_EXT)
+ end
+
def extension_match?(extensions)
return false unless file
diff --git a/app/validators/addressable_url_validator.rb b/app/validators/addressable_url_validator.rb
index 09bfa613cbe..94542125d43 100644
--- a/app/validators/addressable_url_validator.rb
+++ b/app/validators/addressable_url_validator.rb
@@ -18,7 +18,7 @@
# end
#
class AddressableUrlValidator < ActiveModel::EachValidator
- DEFAULT_OPTIONS = { protocols: %w(http https ssh git) }
+ DEFAULT_OPTIONS = { protocols: %w(http https ssh git) }.freeze
def validate_each(record, attribute, value)
unless valid_url?(value)
diff --git a/app/validators/duration_validator.rb b/app/validators/duration_validator.rb
new file mode 100644
index 00000000000..10ff44031c6
--- /dev/null
+++ b/app/validators/duration_validator.rb
@@ -0,0 +1,17 @@
+# DurationValidator
+#
+# Validate the format conforms with ChronicDuration
+#
+# Example:
+#
+# class ApplicationSetting < ActiveRecord::Base
+# validates :default_artifacts_expire_in, presence: true, duration: true
+# end
+#
+class DurationValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ ChronicDuration.parse(value)
+ rescue ChronicDuration::DurationParseError
+ record.errors.add(attribute, "is not a correct duration")
+ end
+end
diff --git a/app/views/admin/abuse_reports/index.html.haml b/app/views/admin/abuse_reports/index.html.haml
index c4b748d0ab8..6c48328da4f 100644
--- a/app/views/admin/abuse_reports/index.html.haml
+++ b/app/views/admin/abuse_reports/index.html.haml
@@ -12,6 +12,7 @@
%th.wide Message
%th Action
= render @abuse_reports
+ = paginate @abuse_reports, theme: 'gitlab'
- else
.empty-state
.text-center
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 816035ec442..057b584e1bc 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -192,7 +192,7 @@
= f.label :max_pages_size, 'Maximum size of pages (MB)', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :max_pages_size, class: 'form-control'
- .help-block Zero for unlimited
+ .help-block 0 for unlimited
%fieldset
%legend Continuous Integration
@@ -212,8 +212,16 @@
.col-sm-10
= f.number_field :max_artifacts_size, class: 'form-control'
.help-block
- Set the maximum file size each jobs's artifacts can have
- = link_to "(?)", help_page_path("user/admin_area/settings/continuous_integration", anchor: "maximum-artifacts-size")
+ Set the maximum file size for each job's artifacts
+ = link_to icon('question-circle'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'maximum-artifacts-size')
+ .form-group
+ = f.label :default_artifacts_expire_in, 'Default artifacts expiration', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.text_field :default_artifacts_expire_in, class: 'form-control'
+ .help-block
+ Set the default expiration time for each job's artifacts.
+ 0 for unlimited.
+ = link_to icon('question-circle'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'default-artifacts-expiration')
- if Gitlab.config.registry.enabled
%fieldset
@@ -525,7 +533,7 @@
= f.number_field :terminal_max_session_time, class: 'form-control'
.help-block
Maximum time for web terminal websocket connection (in seconds).
- Set to 0 for unlimited time.
+ 0 for unlimited.
.form-actions
= f.submit 'Save', class: 'btn btn-save'
diff --git a/app/views/admin/background_jobs/show.html.haml b/app/views/admin/background_jobs/show.html.haml
index 4f982a6e369..ac36bb5bb17 100644
--- a/app/views/admin/background_jobs/show.html.haml
+++ b/app/views/admin/background_jobs/show.html.haml
@@ -35,7 +35,7 @@
.clearfix
%p
%i.fa.fa-exclamation-circle
- If '[25 of 25 busy]' is shown, restart GitLab with 'sudo service gitlab reload'.
+ If '[#{@concurrency} of #{@concurrency} busy]' is shown, restart GitLab with 'sudo service gitlab reload'.
%p
%i.fa.fa-exclamation-circle
If more than one sidekiq process is listed, stop GitLab, kill the remaining sidekiq processes (sudo pkill -u #{gitlab_config.user} -f sidekiq) and restart GitLab.
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 5238623e936..e67ad663720 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -163,6 +163,6 @@
- @groups.each do |group|
%p
= link_to [:admin, group], class: 'str-truncated-60' do
- = group.name
+ = group.full_name
%span.light.pull-right
#{time_ago_with_tooltip(group.created_at)}
diff --git a/app/views/admin/logs/show.html.haml b/app/views/admin/logs/show.html.haml
index 0a954c20fcd..5e585ce789b 100644
--- a/app/views/admin/logs/show.html.haml
+++ b/app/views/admin/logs/show.html.haml
@@ -8,17 +8,16 @@
%div{ class: container_class }
%ul.nav-links.log-tabs
- loggers.each do |klass|
- %li{ class: (klass == Gitlab::GitLogger ? 'active' : '') }>
+ %li{ class: active_when(klass == Gitlab::GitLogger) }>
= link_to klass::file_name, "##{klass::file_name_noext}",
'data-toggle' => 'tab'
.row-content-block
To prevent performance issues admin logs output the last 2000 lines
.tab-content
- loggers.each do |klass|
- .tab-pane{ class: (klass == Gitlab::GitLogger ? 'active' : ''),
- id: klass::file_name_noext }
+ .tab-pane{ class: active_when(klass == Gitlab::GitLogger), id: klass::file_name_noext }
.file-holder#README
- .file-title
+ .js-file-title.file-title
%i.fa.fa-file
= klass::file_name
.pull-right
diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml
index cf8d438670b..c35945c5a35 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -30,7 +30,7 @@
- toggle_text = 'Namespace'
- if params[:namespace_id].present?
- namespace = Namespace.find(params[:namespace_id])
- - toggle_text = "#{namespace.kind}: #{namespace.path}"
+ - toggle_text = "#{namespace.kind}: #{namespace.full_path}"
= dropdown_toggle(toggle_text, { toggle: 'dropdown' }, { toggle_class: 'js-namespace-select large' })
.dropdown-menu.dropdown-select.dropdown-menu-align-right
= dropdown_title('Namespaces')
@@ -48,13 +48,13 @@
= link_to admin_projects_path do
All
- = nav_link(html_options: { class: params[:visibility_level] == Gitlab::VisibilityLevel::PRIVATE.to_s ? 'active' : '' }) do
+ = nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::PRIVATE.to_s) }) do
= link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PRIVATE) do
Private
- = nav_link(html_options: { class: params[:visibility_level] == Gitlab::VisibilityLevel::INTERNAL.to_s ? 'active' : '' }) do
+ = nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::INTERNAL.to_s) }) do
= link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::INTERNAL) do
Internal
- = nav_link(html_options: { class: params[:visibility_level] == Gitlab::VisibilityLevel::PUBLIC.to_s ? 'active' : '' }) do
+ = nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::PUBLIC.to_s) }) do
= link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PUBLIC) do
Public
diff --git a/app/views/admin/runners/_runner.html.haml b/app/views/admin/runners/_runner.html.haml
index 975bd950ae1..deb62845e1c 100644
--- a/app/views/admin/runners/_runner.html.haml
+++ b/app/views/admin/runners/_runner.html.haml
@@ -22,7 +22,7 @@
%td
#{runner.builds.count(:all)}
%td
- - runner.tag_list.each do |tag|
+ - runner.tag_list.sort.each do |tag|
%span.label.label-primary
= tag
%td
diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml
index 721bc77cc2f..d725e477044 100644
--- a/app/views/admin/runners/index.html.haml
+++ b/app/views/admin/runners/index.html.haml
@@ -56,7 +56,7 @@
= submit_tag 'Search', class: 'btn'
.pull-right.light
- Runners with last contact less than a minute ago: #{@active_runners_cnt}
+ Runners with last contact more than a minute ago: #{@active_runners_cnt}
%br
diff --git a/app/views/admin/users/_user.html.haml b/app/views/admin/users/_user.html.haml
index 3b5c713ac2d..a756cb7243a 100644
--- a/app/views/admin/users/_user.html.haml
+++ b/app/views/admin/users/_user.html.haml
@@ -34,7 +34,7 @@
- if user.access_locked?
%li
= link_to 'Unlock', unlock_admin_user_path(user), method: :put, class: 'btn-grouped btn btn-xs btn-success', data: { confirm: 'Are you sure?' }
- - if user.can_be_removed?
+ - if user.can_be_removed? && can?(current_user, :destroy_user, @user)
%li.divider
%li
= link_to 'Delete User', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All issues, merge requests and groups linked to this user will also be removed! Consider cancelling this deletion and blocking the user instead. Are you sure?" },
diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml
index 4dc44225d49..298cf0fa950 100644
--- a/app/views/admin/users/index.html.haml
+++ b/app/views/admin/users/index.html.haml
@@ -38,31 +38,31 @@
.nav-block
%ul.nav-links.wide.scrolling-tabs.white.scrolling-tabs
.fade-left
- = nav_link(html_options: { class: ('active' unless params[:filter]) }) do
+ = nav_link(html_options: { class: active_when(params[:filter].nil?) }) do
= link_to admin_users_path do
Active
%small.badge= number_with_delimiter(User.active.count)
- = nav_link(html_options: { class: ('active' if params[:filter] == 'admins') }) do
+ = nav_link(html_options: { class: active_when(params[:filter] == 'admins') }) do
= link_to admin_users_path(filter: "admins") do
Admins
%small.badge= number_with_delimiter(User.admins.count)
- = nav_link(html_options: { class: "#{'active' if params[:filter] == 'two_factor_enabled'} filter-two-factor-enabled" }) do
+ = nav_link(html_options: { class: "#{active_when(params[:filter] == 'two_factor_enabled')} filter-two-factor-enabled" }) do
= link_to admin_users_path(filter: 'two_factor_enabled') do
2FA Enabled
%small.badge= number_with_delimiter(User.with_two_factor.count)
- = nav_link(html_options: { class: "#{'active' if params[:filter] == 'two_factor_disabled'} filter-two-factor-disabled" }) do
+ = nav_link(html_options: { class: "#{active_when(params[:filter] == 'two_factor_disabled')} filter-two-factor-disabled" }) do
= link_to admin_users_path(filter: 'two_factor_disabled') do
2FA Disabled
%small.badge= number_with_delimiter(User.without_two_factor.count)
- = nav_link(html_options: { class: ('active' if params[:filter] == 'external') }) do
+ = nav_link(html_options: { class: active_when(params[:filter] == 'external') }) do
= link_to admin_users_path(filter: 'external') do
External
%small.badge= number_with_delimiter(User.external.count)
- = nav_link(html_options: { class: ('active' if params[:filter] == 'blocked') }) do
+ = nav_link(html_options: { class: active_when(params[:filter] == 'blocked') }) do
= link_to admin_users_path(filter: "blocked") do
Blocked
%small.badge= number_with_delimiter(User.blocked.count)
- = nav_link(html_options: { class: ('active' if params[:filter] == 'wop') }) do
+ = nav_link(html_options: { class: active_when(params[:filter] == 'wop') }) do
= link_to admin_users_path(filter: "wop") do
Without projects
%small.badge= number_with_delimiter(User.without_projects.count)
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index 76b1291fe10..840d843f069 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -173,7 +173,7 @@
.panel-heading
Remove user
.panel-body
- - if @user.can_be_removed?
+ - if @user.can_be_removed? && can?(current_user, :destroy_user, @user)
%p Deleting a user has the following effects:
%ul
%li All user content like authored issues, snippets, comments will be removed
@@ -189,3 +189,6 @@
%strong= @user.solo_owned_groups.map(&:name).join(', ')
%p
You must transfer ownership or delete these groups before you can delete this user.
+ - else
+ %p
+ You don't have access to delete this user.
diff --git a/app/views/ci/lints/show.html.haml b/app/views/ci/lints/show.html.haml
index b0bee1c6204..dfbc7772698 100644
--- a/app/views/ci/lints/show.html.haml
+++ b/app/views/ci/lints/show.html.haml
@@ -11,7 +11,7 @@
.form-group
.col-sm-12
.file-holder
- .file-title.clearfix
+ .js-file-title.file-title.clearfix
Content of .gitlab-ci.yml
#ci-editor.ci-editor= @content
= text_area_tag(:content, @content, class: 'hidden form-control span1', rows: 7, require: true)
diff --git a/app/views/dashboard/_activity_head.html.haml b/app/views/dashboard/_activity_head.html.haml
index 02b94beee92..ecdf76ef5c5 100644
--- a/app/views/dashboard/_activity_head.html.haml
+++ b/app/views/dashboard/_activity_head.html.haml
@@ -1,7 +1,8 @@
-%ul.nav-links
- %li{ class: ("active" unless params[:filter]) }>
- = link_to activity_dashboard_path, class: 'shortcuts-activity', data: {placement: 'right'} do
- Your Projects
- %li{ class: ("active" if params[:filter] == 'starred') }>
- = link_to activity_dashboard_path(filter: 'starred'), data: {placement: 'right'} do
- Starred Projects
+.top-area
+ %ul.nav-links
+ %li{ class: active_when(params[:filter].nil?) }>
+ = link_to activity_dashboard_path, class: 'shortcuts-activity', data: {placement: 'right'} do
+ Your Projects
+ %li{ class: active_when(params[:filter] == 'starred') }>
+ = link_to activity_dashboard_path(filter: 'starred'), data: {placement: 'right'} do
+ Starred Projects
diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml
index 605bfd0cf8d..a3993d5ef16 100644
--- a/app/views/dashboard/todos/_todo.html.haml
+++ b/app/views/dashboard/todos/_todo.html.haml
@@ -1,28 +1,33 @@
%li{ class: "todo todo-#{todo.done? ? 'done' : 'pending'}", id: dom_id(todo), data: { url: todo_target_path(todo) } }
- = author_avatar(todo, size: 40)
+ .todo-avatar
+ = author_avatar(todo, size: 40)
.todo-item.todo-block
.todo-title.title
- unless todo.build_failed? || todo.unmergeable?
= todo_target_state_pill(todo)
- %span.author-name
+ .title-item.author-name
- if todo.author
= link_to_author(todo)
- else
(removed)
- %span.action-name
+ .title-item.action-name
= todo_action_name(todo)
- %span.todo-label
+ .title-item.todo-label
- if todo.target
= todo_target_link(todo)
- else
(removed)
- &middot; #{time_ago_with_tooltip(todo.created_at)}
- = todo_due_date(todo)
+ .title-item
+ &middot;
+
+ .title-item
+ #{time_ago_with_tooltip(todo.created_at)}
+ = todo_due_date(todo)
.todo-body
.todo-note
@@ -31,6 +36,9 @@
- if todo.pending?
.todo-actions
- = link_to [:dashboard, todo], method: :delete, class: 'btn btn-loading done-todo' do
+ = link_to [:dashboard, todo], method: :delete, class: 'btn btn-loading js-done-todo' do
Done
= icon('spinner spin')
+ = link_to restore_dashboard_todo_path(todo), method: :patch, class: 'btn btn-loading js-undo-todo hidden' do
+ Undo
+ = icon('spinner spin')
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index c4bf2c90cc2..16a5713948a 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -4,15 +4,13 @@
- if current_user.todos.any?
.top-area
%ul.nav-links
- - todo_pending_active = ('active' if params[:state].blank? || params[:state] == 'pending')
- %li{ class: "todos-pending #{todo_pending_active}" }>
+ %li.todos-pending{ class: active_when(params[:state].blank? || params[:state] == 'pending') }>
= link_to todos_filter_path(state: 'pending') do
%span
To do
%span.badge
= number_with_delimiter(todos_pending_count)
- - todo_done_active = ('active' if params[:state] == 'done')
- %li{ class: "todos-done #{todo_done_active}" }>
+ %li.todos-done{ class: active_when(params[:state] == 'done') }>
= link_to todos_filter_path(state: 'done') do
%span
Done
diff --git a/app/views/devise/shared/_signin_box.html.haml b/app/views/devise/shared/_signin_box.html.haml
index eddfce363a7..da4769e214e 100644
--- a/app/views/devise/shared/_signin_box.html.haml
+++ b/app/views/devise/shared/_signin_box.html.haml
@@ -4,7 +4,7 @@
.login-body
= render 'devise/sessions/new_crowd'
- @ldap_servers.each_with_index do |server, i|
- .login-box.tab-pane{ id: "#{server['provider_name']}", role: 'tabpanel', class: (:active if i.zero? && !crowd_enabled?) }
+ .login-box.tab-pane{ id: "#{server['provider_name']}", role: 'tabpanel', class: active_when(i.zero? && !crowd_enabled?) }
.login-body
= render 'devise/sessions/new_ldap', server: server
- if signin_enabled?
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
index 5a44ec45b7b..30e63d991bb 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -8,7 +8,7 @@
= f.text_field :name, class: "form-control top", required: true, title: "This field is required."
.username.form-group
= f.label :username
- = f.text_field :username, class: "form-control middle", pattern: Gitlab::Regex::NAMESPACE_REGEX_STR_SIMPLE, required: true, title: 'Please create a username with only alphanumeric characters.'
+ = f.text_field :username, class: "form-control middle", pattern: Gitlab::Regex::NAMESPACE_REGEX_STR_JS, required: true, title: 'Please create a username with only alphanumeric characters.'
%p.validation-error.hide Username is already taken.
%p.validation-success.hide Username is available.
%p.validation-pending.hide Checking username availability...
diff --git a/app/views/devise/shared/_tabs_ldap.html.haml b/app/views/devise/shared/_tabs_ldap.html.haml
index 8c4ad30c832..dd34600490e 100644
--- a/app/views/devise/shared/_tabs_ldap.html.haml
+++ b/app/views/devise/shared/_tabs_ldap.html.haml
@@ -3,7 +3,7 @@
%li.active
= link_to "Crowd", "#crowd", 'data-toggle' => 'tab'
- @ldap_servers.each_with_index do |server, i|
- %li{ class: (:active if i.zero? && !crowd_enabled?) }
+ %li{ class: active_when(i.zero? && !crowd_enabled?) }
= link_to server['label'], "##{server['provider_name']}", 'data-toggle' => 'tab'
- if signin_enabled?
%li
diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml
index 3a95a652810..94408b92374 100644
--- a/app/views/discussions/_diff_with_notes.html.haml
+++ b/app/views/discussions/_diff_with_notes.html.haml
@@ -2,7 +2,7 @@
- blob = discussion.blob
.diff-file.file-holder
- .file-title
+ .js-file-title.file-title
= render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_file.content_commit, project: discussion.project, url: discussion_diff_path(discussion)
.diff-content.code.js-syntax-highlight
diff --git a/app/views/discussions/_resolve_all.html.haml b/app/views/discussions/_resolve_all.html.haml
index f0b61e0f7de..e30ee1b0e05 100644
--- a/app/views/discussions/_resolve_all.html.haml
+++ b/app/views/discussions/_resolve_all.html.haml
@@ -1,6 +1,5 @@
- if discussion.for_merge_request?
- %resolve-discussion-btn{ ":project-path" => "'#{project_path(discussion.project)}'",
- ":discussion-id" => "'#{discussion.id}'",
+ %resolve-discussion-btn{ ":discussion-id" => "'#{discussion.id}'",
":merge-request-id" => discussion.noteable.iid,
":can-resolve" => discussion.can_resolve?(current_user),
"inline-template" => true }
diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml
index 64ca3c32e01..efd13aabf20 100644
--- a/app/views/events/event/_push.html.haml
+++ b/app/views/events/event/_push.html.haml
@@ -3,11 +3,9 @@
.event-title
%span.author_name= link_to_author event
%span.pushed #{event.action_name} #{event.ref_type}
- - if event.rm_ref?
- %strong= event.ref_name
- - else
- %strong
- = link_to event.ref_name, namespace_project_commits_path(project.namespace, project, event.ref_name), title: h(event.target_title)
+ %strong
+ - commits_link = namespace_project_commits_path(project.namespace, project, event.ref_name)
+ = link_to_if project.repository.branch_exists?(event.ref_name), event.ref_name, commits_link
= render "events/event_scope", event: event
diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml
index e3088848492..56f463572bb 100644
--- a/app/views/explore/projects/_filter.html.haml
+++ b/app/views/explore/projects/_filter.html.haml
@@ -13,7 +13,7 @@
= link_to filter_projects_path(visibility_level: nil) do
Any
- Gitlab::VisibilityLevel.values.each do |level|
- %li{ class: (level.to_s == params[:visibility_level]) ? 'active' : 'light' }
+ %li{ class: active_when(level.to_s == params[:visibility_level]) || 'light' }
= link_to filter_projects_path(visibility_level: level) do
= visibility_level_icon(level)
= visibility_level_label(level)
@@ -34,7 +34,7 @@
Any
- @tags.each do |tag|
- %li{ class: (tag.name == params[:tag]) ? 'active' : 'light' }
+ %li{ class: active_when(tag.name == params[:tag]) || 'light' }
= link_to filter_projects_path(tag: tag.name) do
= icon('tag')
= tag.name
diff --git a/app/views/groups/_head.html.haml b/app/views/groups/_head.html.haml
new file mode 100644
index 00000000000..6b296ea8dea
--- /dev/null
+++ b/app/views/groups/_head.html.haml
@@ -0,0 +1,19 @@
+= content_for :sub_nav do
+ .scrolling-tabs-container.sub-nav-scroll
+ = render 'shared/nav_scroll'
+ .nav-links.sub-nav.scrolling-tabs
+ %ul{ class: container_class }
+ = nav_link(path: 'groups#show', html_options: { class: 'home' }) do
+ = link_to group_path(@group), title: 'Group Home' do
+ %span
+ Home
+
+ = nav_link(path: 'groups#activity') do
+ = link_to activity_group_path(@group), title: 'Activity' do
+ %span
+ Activity
+
+ = nav_link(path: 'group_members#index') do
+ = link_to group_group_members_path(@group), title: 'Members' do
+ %span
+ Members
diff --git a/app/views/groups/_head_issues.html.haml b/app/views/groups/_head_issues.html.haml
new file mode 100644
index 00000000000..d554bc23743
--- /dev/null
+++ b/app/views/groups/_head_issues.html.haml
@@ -0,0 +1,19 @@
+= content_for :sub_nav do
+ .scrolling-tabs-container.sub-nav-scroll
+ = render 'shared/nav_scroll'
+ .nav-links.sub-nav.scrolling-tabs
+ %ul{ class: container_class }
+ = nav_link(path: 'groups#issues', html_options: { class: 'home' }) do
+ = link_to issues_group_path(@group), title: 'List' do
+ %span
+ List
+
+ = nav_link(path: 'labels#index') do
+ = link_to group_labels_path(@group), title: 'Labels' do
+ %span
+ Labels
+
+ = nav_link(path: 'milestones#index') do
+ = link_to group_milestones_path(@group), title: 'Milestones' do
+ %span
+ Milestones
diff --git a/app/views/groups/_home_panel.html.haml b/app/views/groups/_home_panel.html.haml
new file mode 100644
index 00000000000..41f54f6bf42
--- /dev/null
+++ b/app/views/groups/_home_panel.html.haml
@@ -0,0 +1,17 @@
+.group-home-panel.text-center
+ %div{ class: container_class }
+ .avatar-container.s70.group-avatar
+ = image_tag group_icon(@group), class: "avatar s70 avatar-tile"
+ %h1.group-title
+ @#{@group.path}
+ %span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) }
+ = visibility_level_icon(@group.visibility_level, fw: false)
+
+ - if @group.description.present?
+ .group-home-desc
+ = markdown_field(@group, :description)
+
+ - if current_user
+ .group-buttons
+ = render 'shared/members/access_request_buttons', source: @group
+ = render 'shared/notifications/button', notification_setting: @notification_setting
diff --git a/app/views/groups/_show_nav.html.haml b/app/views/groups/_show_nav.html.haml
new file mode 100644
index 00000000000..b2097e88741
--- /dev/null
+++ b/app/views/groups/_show_nav.html.haml
@@ -0,0 +1,7 @@
+%ul.nav-links
+ = nav_link(page: group_path(@group)) do
+ = link_to group_path(@group) do
+ Projects
+ = nav_link(page: subgroups_group_path(@group)) do
+ = link_to subgroups_group_path(@group) do
+ Subgroups
diff --git a/app/views/groups/activity.html.haml b/app/views/groups/activity.html.haml
index aaad265b3ee..d7375b23524 100644
--- a/app/views/groups/activity.html.haml
+++ b/app/views/groups/activity.html.haml
@@ -2,7 +2,8 @@
- if current_user
= auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity")
-- page_title "Activity"
+- page_title "Activity"
+= render 'groups/head'
%section.activities
= render 'activities'
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index 2e4e4511bb6..8cb56443191 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -1,4 +1,5 @@
- page_title "Members"
+= render 'groups/head'
.project-members-page.prepend-top-default
%h4
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index 83edb719692..939bddf3fe9 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -1,4 +1,5 @@
- page_title "Issues"
+= render "head_issues"
= content_for :meta_tags do
- if current_user
= auto_discovery_link_tag(:atom, url_for(params.merge(format: :atom, private_token: current_user.private_token)), title: "#{@group.name} issues")
diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml
index 45325d6bc4b..2bc00fb16c8 100644
--- a/app/views/groups/labels/index.html.haml
+++ b/app/views/groups/labels/index.html.haml
@@ -1,4 +1,5 @@
- page_title 'Labels'
+= render "groups/head_issues"
.top-area.adjust
.nav-text
diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml
index cd5388fe402..644895c56a1 100644
--- a/app/views/groups/milestones/index.html.haml
+++ b/app/views/groups/milestones/index.html.haml
@@ -1,4 +1,5 @@
- page_title "Milestones"
+= render "groups/head_issues"
.top-area
= render 'shared/milestones_filter'
diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml
index fb6f0da28f8..e66a8e0a3b3 100644
--- a/app/views/groups/milestones/show.html.haml
+++ b/app/views/groups/milestones/show.html.haml
@@ -1,4 +1,8 @@
= render "header_title"
+
+- content_for :page_specific_javascripts do
+ = page_specific_javascript_bundle_tag('simulate_drag') if Rails.env.test?
+
= render 'shared/milestones/top', milestone: @milestone, group: @group
= render 'shared/milestones/summary', milestone: @milestone
= render 'shared/milestones/tabs', milestone: @milestone, show_project_name: true
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index d256d14609e..3d7b469660a 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -4,38 +4,13 @@
- if current_user
= auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity")
-.group-home-panel.text-center
- %div{ class: container_class }
- .avatar-container.s70.group-avatar
- = image_tag group_icon(@group), class: "avatar s70 avatar-tile"
- %h1.group-title
- @#{@group.path}
- %span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) }
- = visibility_level_icon(@group.visibility_level, fw: false)
+= render 'groups/head'
+= render 'groups/home_panel'
- - if @group.description.present?
- .group-home-desc
- = markdown_field(@group, :description)
-
- - if current_user
- .group-buttons
- = render 'shared/members/access_request_buttons', source: @group
- = render 'shared/notifications/button', notification_setting: @notification_setting
.groups-header{ class: container_class }
.top-area
- %ul.nav-links
- %li.active
- = link_to "#projects", 'data-toggle' => 'tab' do
- All Projects
- - if @shared_projects.present?
- %li
- = link_to "#shared", 'data-toggle' => 'tab' do
- Shared Projects
- - if @nested_groups.present?
- %li
- = link_to "#groups", 'data-toggle' => 'tab' do
- Subgroups
+ = render 'groups/show_nav'
.nav-controls
= form_tag request.path, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
@@ -44,15 +19,4 @@
= link_to new_project_path(namespace_id: @group.id), class: 'btn btn-new pull-right' do
New Project
- .tab-content
- .tab-pane.active#projects
- = render "projects", projects: @projects
-
- - if @shared_projects.present?
- .tab-pane#shared
- = render "shared_projects", projects: @shared_projects
-
- - if @nested_groups.present?
- .tab-pane#groups
- %ul.content-list
- = render partial: 'shared/groups/group', collection: @nested_groups
+ = render "projects", projects: @projects
diff --git a/app/views/groups/subgroups.html.haml b/app/views/groups/subgroups.html.haml
new file mode 100644
index 00000000000..8610ae7e0ef
--- /dev/null
+++ b/app/views/groups/subgroups.html.haml
@@ -0,0 +1,20 @@
+- @no_container = true
+
+= render 'groups/home_panel'
+
+.groups-header{ class: container_class }
+ .top-area
+ = render 'groups/show_nav'
+ .nav-controls
+ = form_tag request.path, method: :get do |f|
+ = search_field_tag :filter_groups, params[:filter_groups], placeholder: 'Filter by name', class: 'form-control', spellcheck: false
+ - if can? current_user, :admin_group, @group
+ = link_to new_group_path(parent_id: @group.id), class: 'btn btn-new pull-right' do
+ New Subgroup
+
+ - if @nested_groups.present?
+ %ul.content-list
+ = render partial: 'shared/groups/group', collection: @nested_groups, locals: { full_name: false }
+ - else
+ .nothing-here-block
+ There are no subgroups to show.
diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml
index dd1df46792b..87f9b503989 100644
--- a/app/views/help/ui.html.haml
+++ b/app/views/help/ui.html.haml
@@ -528,7 +528,7 @@
- blob = Snippet.new(content: "Wow\nSuch\nFile")
.example
.file-holder
- .file-title
+ .js-file-title.file-title
Awesome file
.file-actions
.btn-group
diff --git a/app/views/import/base/unauthorized.js.haml b/app/views/import/base/unauthorized.js.haml
index 36f8069c1f7..ada5f99f4e2 100644
--- a/app/views/import/base/unauthorized.js.haml
+++ b/app/views/import/base/unauthorized.js.haml
@@ -4,7 +4,7 @@
import_button = tr.find(".btn-import")
origin_target = target_field.text()
project_name = "#{@project_name}"
- origin_namespace = "#{@target_namespace.path}"
+ origin_namespace = "#{@target_namespace.full_path}"
target_field.empty()
target_field.append("<p class='alert alert-danger'>This namespace has already been taken! Please choose another one.</p>")
target_field.append("<input type='text' name='target_namespace' />")
diff --git a/app/views/kaminari/gitlab/_page.html.haml b/app/views/kaminari/gitlab/_page.html.haml
index cefe0066a8f..5c5be03a7cd 100644
--- a/app/views/kaminari/gitlab/_page.html.haml
+++ b/app/views/kaminari/gitlab/_page.html.haml
@@ -6,5 +6,5 @@
-# total_pages: total number of pages
-# per_page: number of items to fetch per page
-# remote: data-remote
-%li{ class: "page#{' active' if page.current?}#{' sibling' if page.next? || page.prev?}" }
+%li.page{ class: [active_when(page.current?), ('sibling' if page.next? || page.prev?)] }
= link_to page, url, { remote: remote, rel: page.next? ? 'next' : page.prev? ? 'prev' : nil }
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index f2d355587bd..302c1794628 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -23,7 +23,7 @@
%title= page_title(site_name)
%meta{ name: "description", content: page_description }
- = favicon_link_tag 'favicon.ico'
+ = favicon_link_tag favicon
= stylesheet_link_tag "application", media: "all"
= stylesheet_link_tag "print", media: "print"
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 54d02ee8e4b..a35a918d501 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -1,21 +1,4 @@
-.page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" }
- .sidebar-wrapper.nicescroll
- .sidebar-action-buttons
- .nav-header-btn.toggle-nav-collapse{ title: "Open/Close" }
- %span.sr-only Toggle navigation
- = icon('bars')
-
- %div{ class: "nav-header-btn pin-nav-btn has-tooltip #{'is-active' if pinned_nav?} js-nav-pin", title: pinned_nav? ? "Unpin navigation" : "Pin Navigation", data: { placement: 'right', container: 'body' } }
- %span.sr-only Toggle navigation pinning
- = icon('fw thumb-tack')
-
- - if defined?(sidebar) && sidebar
- = render "layouts/nav/#{sidebar}"
- - elsif current_user
- = render 'layouts/nav/dashboard'
- - else
- = render 'layouts/nav/explore'
-
+.page-with-sidebar{ class: page_gutter_class }
- if defined?(nav) && nav
.layout-nav
.container-fluid
diff --git a/app/views/layouts/_recaptcha_verification.html.haml b/app/views/layouts/_recaptcha_verification.html.haml
new file mode 100644
index 00000000000..77c77dc6754
--- /dev/null
+++ b/app/views/layouts/_recaptcha_verification.html.haml
@@ -0,0 +1,23 @@
+- humanized_resource_name = spammable.class.model_name.human.downcase
+- resource_name = spammable.class.model_name.singular
+
+%h3.page-title
+ Anti-spam verification
+%hr
+
+%p
+ #{"We detected potential spam in the #{humanized_resource_name}. Please solve the reCAPTCHA to proceed."}
+
+= form_for form do |f|
+ .recaptcha
+ - params[resource_name].each do |field, value|
+ = hidden_field(resource_name, field, value: value)
+ = hidden_field_tag(:spam_log_id, spammable.spam_log.id)
+ = hidden_field_tag(:recaptcha_verification, true)
+ = recaptcha_tags
+
+ -# Yields a block with given extra params.
+ = yield
+
+ .row-content-block.footer-block
+ = f.submit "Submit #{humanized_resource_name}", class: 'btn btn-create'
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 248d439cd05..19bd9b6d5c9 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -1,7 +1,7 @@
!!! 5
%html{ lang: "en", class: "#{page_class}" }
= render "layouts/head"
- %body{ class: "#{user_application_theme}", data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}" } }
+ %body{ data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}" } }
= Gon::Base.render_data
= render "layouts/header/default", title: header_title
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 9ecc0d11c95..0b8388cbff3 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -1,10 +1,16 @@
-%header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class }
+%header.navbar.navbar-gitlab{ class: nav_header_class }
%a.sr-only.gl-accessibility{ href: "#content-body", tabindex: "1" } Skip to content
.container-fluid
.header-content
- %button.side-nav-toggle{ type: 'button', "aria-label" => "Toggle global navigation" }
- %span.sr-only Toggle navigation
- = icon('bars')
+ .dropdown.global-dropdown
+ %button.global-dropdown-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
+ %span.sr-only Toggle navigation
+ = icon('bars')
+ .dropdown-menu-nav.global-dropdown-menu
+ - if current_user
+ = render 'layouts/nav/dashboard'
+ - else
+ = render 'layouts/nav/explore'
%button.navbar-toggle{ type: 'button' }
%span.sr-only Toggle navigation
= icon('ellipsis-v')
@@ -29,7 +35,7 @@
= link_to dashboard_todos_path, title: 'Todos', aria: { label: "Todos" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('bell fw')
%span.badge.todos-pending-count{ class: ("hidden" if todos_pending_count == 0) }
- = todos_pending_count
+ = todos_count_format(todos_pending_count)
- if Gitlab::Sherlock.enabled?
%li
= link_to sherlock_transactions_path, title: 'Sherlock Transactions',
@@ -55,7 +61,6 @@
%div
= link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-success'
-
%h1.title= title
.header-logo
diff --git a/app/views/layouts/mailer.html.haml b/app/views/layouts/mailer.html.haml
new file mode 100644
index 00000000000..53268cc22f8
--- /dev/null
+++ b/app/views/layouts/mailer.html.haml
@@ -0,0 +1,72 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+%html{ lang: "en" }
+ %head
+ %meta{ content: "text/html; charset=UTF-8", "http-equiv" => "Content-Type" }/
+ %meta{ content: "width=device-width, initial-scale=1", name: "viewport" }/
+ %meta{ content: "IE=edge", "http-equiv" => "X-UA-Compatible" }/
+ %title= message.subject
+ :css
+ /* CLIENT-SPECIFIC STYLES */
+ body, table, td, a { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
+ table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; }
+ img { -ms-interpolation-mode: bicubic; }
+
+ /* iOS BLUE LINKS */
+ a[x-apple-data-detectors] {
+ color: inherit !important;
+ text-decoration: none !important;
+ font-size: inherit !important;
+ font-family: inherit !important;
+ font-weight: inherit !important;
+ line-height: inherit !important;
+ }
+
+ /* ANDROID MARGIN HACK */
+ body { margin:0 !important; }
+ div[style*="margin: 16px 0"] { margin:0 !important; }
+
+ @media only screen and (max-width: 639px) {
+ body, #body {
+ min-width: 320px !important;
+ }
+ table.wrapper {
+ width: 100% !important;
+ min-width: 320px !important;
+ }
+ table.wrapper > tbody > tr > td {
+ border-left: 0 !important;
+ border-right: 0 !important;
+ border-radius: 0 !important;
+ padding-left: 10px !important;
+ padding-right: 10px !important;
+ }
+ }
+ %body{ style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;height:100%;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" }
+ %table#body{ border: "0", cellpadding: "0", cellspacing: "0", style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;" }
+ %tbody
+ %tr.line
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#6b4fbb;height:4px;font-size:4px;line-height:4px;" }  
+ %tr.header
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
+ = header_logo
+ %tr
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" }
+ %table.wrapper{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:640px;margin:0 auto;border-collapse:separate;border-spacing:0;" }
+ %tbody
+ %tr
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#ffffff;text-align:left;padding:18px 25px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" }
+ %table.content{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;border-collapse:separate;border-spacing:0;" }
+ %tbody
+ = yield
+
+ %tr.footer
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
+ %img{ alt: "GitLab", height: "33", src: image_url('mailers/gitlab_footer_logo.gif'), style: "display:block;margin:0 auto 1em;", width: "90" }/
+ %div
+ %a{ href: profile_notifications_url, style: "color:#3777b0;text-decoration:none;" } Manage all notifications
+ &middot;
+ %a{ href: help_url, style: "color:#3777b0;text-decoration:none;" } Help
+ %div
+ You're receiving this email because of your account on
+ = succeed "." do
+ %a{ href: root_url, style: "color:#3777b0;text-decoration:none;" }= Gitlab.config.gitlab.host
diff --git a/app/views/layouts/mailer.text.haml b/app/views/layouts/mailer.text.haml
new file mode 100644
index 00000000000..6a9c6ced9cc
--- /dev/null
+++ b/app/views/layouts/mailer.text.haml
@@ -0,0 +1,5 @@
+= yield
+
+You're receiving this email because of your account on #{Gitlab.config.gitlab.host}.
+Manage all notifications: #{profile_notifications_url}
+Help: #{help_url}
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index 19a947af4ca..d068c895fa3 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -33,7 +33,7 @@
Abuse Reports
%span.badge.count= number_with_delimiter(AbuseReport.count(:all))
- - if askimet_enabled?
+ - if akismet_enabled?
= nav_link(controller: :spam_logs) do
= link_to admin_spam_logs_path, title: "Spam Logs" do
%span
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 205d23178d2..5d4178f03d7 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -1,41 +1,39 @@
-.nav-sidebar
- %ul.nav
- = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do
- = link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
- %span
- Projects
- = nav_link(path: 'dashboard#activity') do
- = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do
- %span
- Activity
- - if koding_enabled?
- = nav_link(controller: :koding) do
- = link_to koding_path, title: 'Koding' do
- %span
- Koding
- = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
- = link_to dashboard_groups_path, title: 'Groups' do
- %span
- Groups
- = nav_link(controller: 'dashboard/milestones') do
- = link_to dashboard_milestones_path, title: 'Milestones' do
- %span
- Milestones
- = nav_link(path: 'dashboard#issues') do
- = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do
- %span
- Issues
- %span.count= number_with_delimiter(cached_assigned_issuables_count(current_user, :issues, :opened))
- = nav_link(path: 'dashboard#merge_requests') do
- = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do
- %span
- Merge Requests
- %span.count= number_with_delimiter(cached_assigned_issuables_count(current_user, :merge_requests, :opened))
- = nav_link(controller: 'dashboard/snippets') do
- = link_to dashboard_snippets_path, title: 'Snippets' do
+%ul
+ = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do
+ = link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
+ %span
+ Projects
+ = nav_link(path: 'dashboard#activity') do
+ = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do
+ %span
+ Activity
+ - if koding_enabled?
+ = nav_link(controller: :koding) do
+ = link_to koding_path, title: 'Koding' do
%span
- Snippets
-
- = link_to help_path, title: 'About GitLab CE', class: 'about-gitlab' do
+ Koding
+ = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
+ = link_to dashboard_groups_path, title: 'Groups' do
+ %span
+ Groups
+ = nav_link(controller: 'dashboard/milestones') do
+ = link_to dashboard_milestones_path, title: 'Milestones' do
+ %span
+ Milestones
+ = nav_link(path: 'dashboard#issues') do
+ = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do
+ %span
+ Issues
+ (#{number_with_delimiter(cached_assigned_issuables_count(current_user, :issues, :opened))})
+ = nav_link(path: 'dashboard#merge_requests') do
+ = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do
+ %span
+ Merge Requests
+ (#{number_with_delimiter(cached_assigned_issuables_count(current_user, :merge_requests, :opened))})
+ = nav_link(controller: 'dashboard/snippets') do
+ = link_to dashboard_snippets_path, title: 'Snippets' do
%span
- About GitLab CE
+ Snippets
+ %li.divider
+ %li
+ = link_to "About GitLab CE", help_path, title: 'About GitLab CE', class: 'about-gitlab'
diff --git a/app/views/layouts/nav/_explore.html.haml b/app/views/layouts/nav/_explore.html.haml
index e5bda7b3a6f..3a1fcd00e9c 100644
--- a/app/views/layouts/nav/_explore.html.haml
+++ b/app/views/layouts/nav/_explore.html.haml
@@ -1,4 +1,4 @@
-%ul.nav.nav-sidebar
+%ul
= nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do
= link_to explore_root_path, title: 'Projects' do
%span
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index f3539fd372d..e0742d70fac 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -5,23 +5,11 @@
.fade-right
= icon('angle-right')
%ul.nav-links.scrolling-tabs
- = nav_link(path: 'groups#show', html_options: {class: 'home'}) do
+ = nav_link(path: ['groups#show', 'groups#activity', 'group_members#index'], html_options: { class: 'home' }) do
= link_to group_path(@group), title: 'Home' do
%span
Group
- = nav_link(path: 'groups#activity') do
- = link_to activity_group_path(@group), title: 'Activity' do
- %span
- Activity
- = nav_link(controller: [:group, :labels]) do
- = link_to group_labels_path(@group), title: 'Labels' do
- %span
- Labels
- = nav_link(controller: [:group, :milestones]) do
- = link_to group_milestones_path(@group), title: 'Milestones' do
- %span
- Milestones
- = nav_link(path: 'groups#issues') do
+ = nav_link(path: ['groups#issues', 'labels#index', 'milestones#index']) do
= link_to issues_group_path(@group), title: 'Issues' do
%span
Issues
@@ -33,7 +21,3 @@
Merge Requests
- merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute
%span.badge.count= number_with_delimiter(merge_requests.count)
- = nav_link(controller: [:group_members]) do
- = link_to group_group_members_path(@group), title: 'Members' do
- %span
- Members
diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml
index d9ebbaa2704..85a1aea3a61 100644
--- a/app/views/notify/pipeline_failed_email.html.haml
+++ b/app/views/notify/pipeline_failed_email.html.haml
@@ -1,179 +1,109 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-%html{ lang: "en" }
- %head
- %meta{ content: "text/html; charset=UTF-8", "http-equiv" => "Content-Type" }/
- %meta{ content: "width=device-width, initial-scale=1", name: "viewport" }/
- %meta{ content: "IE=edge", "http-equiv" => "X-UA-Compatible" }/
- %title= message.subject
- :css
- /* CLIENT-SPECIFIC STYLES */
- body, table, td, a { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
- table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; }
- img { -ms-interpolation-mode: bicubic; }
-
- /* iOS BLUE LINKS */
- a[x-apple-data-detectors] {
- color: inherit !important;
- text-decoration: none !important;
- font-size: inherit !important;
- font-family: inherit !important;
- font-weight: inherit !important;
- line-height: inherit !important;
- }
-
- /* ANDROID MARGIN HACK */
- body { margin:0 !important; }
- div[style*="margin: 16px 0"] { margin:0 !important; }
-
- @media only screen and (max-width: 639px) {
- body, #body {
- min-width: 320px !important;
- }
- table.wrapper {
- width: 100% !important;
- min-width: 320px !important;
- }
- table.wrapper > tbody > tr > td {
- border-left: 0 !important;
- border-right: 0 !important;
- border-radius: 0 !important;
- padding-left: 10px !important;
- padding-right: 10px !important;
- }
- }
- %body{ style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;height:100%;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" }
- %table#body{ border: "0", cellpadding: "0", cellspacing: "0", style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;" }
+%tr.alert
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;background-color:#d22f57;color:#ffffff;" }
+ %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;margin:0 auto;" }
%tbody
- %tr.line
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#6b4fbb;height:4px;font-size:4px;line-height:4px;" }  
- %tr.header
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
- %img{ alt: "GitLab", height: "50", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo.gif'), width: "55" }/
%tr
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" }
- %table.wrapper{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:640px;margin:0 auto;border-collapse:separate;border-spacing:0;" }
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;" }
+ %img{ alt: "x", height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-x-red-inverted.gif'), style: "display:block;", width: "13" }/
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;" }
+ Your pipeline has failed.
+%tr.spacer
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" }
+ &nbsp;
+%tr.section
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" }
+ %table.info{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;" }
+ %tbody
+ %tr
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;" } Project
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;" }
+ - namespace_name = @project.group ? @project.group.name : @project.namespace.owner.name
+ - namespace_url = @project.group ? group_url(@project.group) : user_url(@project.namespace.owner)
+ %a.muted{ href: namespace_url, style: "color:#333333;text-decoration:none;" }
+ = namespace_name
+ \/
+ %a.muted{ href: project_url(@project), style: "color:#333333;text-decoration:none;" }
+ = @project.name
+ %tr
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Branch
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
+ %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
+ %tbody
+ %tr
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
+ %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), style: "display:block;", width: "13", alt: "Branch icon" }/
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
+ %a.muted{ href: commits_url(@pipeline), style: "color:#333333;text-decoration:none;" }
+ = @pipeline.ref
+ %tr
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Commit
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
+ %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
%tbody
%tr
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#ffffff;text-align:left;padding:18px 25px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" }
- %table.content{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;border-collapse:separate;border-spacing:0;" }
- %tbody
- %tr.alert
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;background-color:#d22f57;color:#ffffff;" }
- %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;margin:0 auto;" }
- %tbody
- %tr
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;" }
- %img{ alt: "x", height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-x-red-inverted.gif'), style: "display:block;", width: "13" }/
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;" }
- Your pipeline has failed.
- %tr.spacer
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" }
- &nbsp;
- %tr.section
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" }
- %table.info{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;" }
- %tbody
- %tr
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;" } Project
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;" }
- - namespace_name = @project.group ? @project.group.name : @project.namespace.owner.name
- - namespace_url = @project.group ? group_url(@project.group) : user_url(@project.namespace.owner)
- %a.muted{ href: namespace_url, style: "color:#333333;text-decoration:none;" }
- = namespace_name
- \/
- %a.muted{ href: project_url(@project), style: "color:#333333;text-decoration:none;" }
- = @project.name
- %tr
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Branch
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
- %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
- %tbody
- %tr
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
- %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), style: "display:block;", width: "13", alt: "Branch icon" }/
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
- %a.muted{ href: commits_url(@pipeline), style: "color:#333333;text-decoration:none;" }
- = @pipeline.ref
- %tr
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Commit
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
- %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
- %tbody
- %tr
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
- %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-commit-gray.gif'), style: "display:block;", width: "13", alt: "Commit icon" }/
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
- %a{ href: commit_url(@pipeline), style: "color:#3777b0;text-decoration:none;" }
- = @pipeline.short_sha
- - if @merge_request
- in
- %a{ href: merge_request_url(@merge_request), style: "color:#3777b0;text-decoration:none;" }
- = @merge_request.to_reference
- .commit{ style: "color:#5c5c5c;font-weight:300;" }
- = @pipeline.git_commit_message.truncate(50)
- %tr
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Author
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
- %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
- %tbody
- %tr
- - commit = @pipeline.commit
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
- %img.avatar{ height: "24", src: avatar_icon(commit.author || commit.author_email, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
- - if commit.author
- %a.muted{ href: user_url(commit.author), style: "color:#333333;text-decoration:none;" }
- = commit.author.name
- - else
- %span
- = commit.author_name
- %tr.spacer
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" }
- &nbsp;
- - failed = @pipeline.statuses.latest.failed
- %tr.pre-section
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 0;" }
- Pipeline
- %a{ href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;" }
- = "\##{@pipeline.id}"
- had
- = failed.size
- failed
- #{'build'.pluralize(failed.size)}.
- %tr.warning
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;border:1px solid #ededed;border-bottom:0;border-radius:3px 3px 0 0;overflow:hidden;background-color:#fdf4f6;color:#d22852;font-size:14px;line-height:1.4;text-align:center;padding:8px 15px;" }
- Logs may contain sensitive data. Please consider before forwarding this email.
- %tr.section
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;border-top:0;border-radius:0 0 3px 3px;" }
- %table.builds{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;border-collapse:collapse;" }
- %tbody
- - failed.each do |build|
- %tr.build-state
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:20px 0;color:#8c8c8c;font-weight:500;font-size:15px;" }
- %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
- %tbody
- %tr
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#8c8c8c;font-weight:500;font-size:15px;vertical-align:middle;padding-right:5px;" }
- %img{ alt: "x", height: "10", src: image_url('mailers/ci_pipeline_notif_v1/icon-x-red.gif'), style: "display:block;", width: "10" }/
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#8c8c8c;font-weight:500;font-size:15px;vertical-align:middle;" }
- = build.stage
- %td{ align: "right", style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:20px 0;color:#8c8c8c;font-weight:500;font-size:15px;" }
- = render "notify/links/#{build.to_partial_path}", pipeline: @pipeline, build: build
- %tr.build-log
- - if build.has_trace?
- %td{ colspan: "2", style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 0 15px;" }
- %pre{ style: "font-family:Monaco,'Lucida Console','Courier New',Courier,monospace;background-color:#fafafa;border-radius:3px;overflow:hidden;white-space:pre-wrap;word-break:break-all;font-size:13px;line-height:1.4;padding:12px;color:#333333;margin:0;" }
- = build.trace_html(last_lines: 10).html_safe
- - else
- %td{ colspan: "2" }
- %tr.footer
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
- %img{ alt: "GitLab", height: "33", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif'), style: "display:block;margin:0 auto 1em;", width: "90" }/
- %div
- %a{ href: profile_notifications_url, style: "color:#3777b0;text-decoration:none;" } Manage all notifications
- &middot;
- %a{ href: help_url, style: "color:#3777b0;text-decoration:none;" } Help
- %div
- You're receiving this email because of your account on
- = succeed "." do
- %a{ href: root_url, style: "color:#3777b0;text-decoration:none;" }= Gitlab.config.gitlab.host
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
+ %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-commit-gray.gif'), style: "display:block;", width: "13", alt: "Commit icon" }/
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
+ %a{ href: commit_url(@pipeline), style: "color:#3777b0;text-decoration:none;" }
+ = @pipeline.short_sha
+ - if @merge_request
+ in
+ %a{ href: merge_request_url(@merge_request), style: "color:#3777b0;text-decoration:none;" }
+ = @merge_request.to_reference
+ .commit{ style: "color:#5c5c5c;font-weight:300;" }
+ = @pipeline.git_commit_message.truncate(50)
+ %tr
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Author
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
+ %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
+ %tbody
+ %tr
+ - commit = @pipeline.commit
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
+ %img.avatar{ height: "24", src: avatar_icon(commit.author || commit.author_email, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
+ - if commit.author
+ %a.muted{ href: user_url(commit.author), style: "color:#333333;text-decoration:none;" }
+ = commit.author.name
+ - else
+ %span
+ = commit.author_name
+%tr.spacer
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" }
+ &nbsp;
+- failed = @pipeline.statuses.latest.failed
+%tr.pre-section
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 0;" }
+ Pipeline
+ %a{ href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;" }
+ = "\##{@pipeline.id}"
+ had
+ = failed.size
+ failed
+ #{'build'.pluralize(failed.size)}.
+%tr.warning
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;border:1px solid #ededed;border-bottom:0;border-radius:3px 3px 0 0;overflow:hidden;background-color:#fdf4f6;color:#d22852;font-size:14px;line-height:1.4;text-align:center;padding:8px 15px;" }
+ Logs may contain sensitive data. Please consider before forwarding this email.
+%tr.section
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;border-top:0;border-radius:0 0 3px 3px;" }
+ %table.builds{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;border-collapse:collapse;" }
+ %tbody
+ - failed.each do |build|
+ %tr.build-state
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:20px 0;color:#8c8c8c;font-weight:500;font-size:15px;" }
+ %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
+ %tbody
+ %tr
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#8c8c8c;font-weight:500;font-size:15px;vertical-align:middle;padding-right:5px;" }
+ %img{ alt: "x", height: "10", src: image_url('mailers/ci_pipeline_notif_v1/icon-x-red.gif'), style: "display:block;", width: "10" }/
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#8c8c8c;font-weight:500;font-size:15px;vertical-align:middle;" }
+ = build.stage
+ %td{ align: "right", style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:20px 0;color:#8c8c8c;font-weight:500;font-size:15px;" }
+ = render "notify/links/#{build.to_partial_path}", pipeline: @pipeline, build: build
+ %tr.build-log
+ - if build.has_trace?
+ %td{ colspan: "2", style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 0 15px;" }
+ %pre{ style: "font-family:Monaco,'Lucida Console','Courier New',Courier,monospace;background-color:#fafafa;border-radius:3px;overflow:hidden;white-space:pre-wrap;word-break:break-all;font-size:13px;line-height:1.4;padding:12px;color:#333333;margin:0;" }
+ = build.trace_html(last_lines: 10).html_safe
+ - else
+ %td{ colspan: "2" }
diff --git a/app/views/notify/pipeline_failed_email.text.erb b/app/views/notify/pipeline_failed_email.text.erb
index ab91c7ef350..520a2fc7d68 100644
--- a/app/views/notify/pipeline_failed_email.text.erb
+++ b/app/views/notify/pipeline_failed_email.text.erb
@@ -27,7 +27,3 @@ Trace: <%= build.trace_with_state(last_lines: 10)[:text] %>
<% end -%>
<% end -%>
-
-You're receiving this email because of your account on <%= Gitlab.config.gitlab.host %>.
-Manage all notifications: <%= profile_notifications_url %>
-Help: <%= help_url %>
diff --git a/app/views/notify/pipeline_success_email.html.haml b/app/views/notify/pipeline_success_email.html.haml
index 8add2e18206..19d4add06f5 100644
--- a/app/views/notify/pipeline_success_email.html.haml
+++ b/app/views/notify/pipeline_success_email.html.haml
@@ -1,154 +1,84 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-%html{ lang: "en" }
- %head
- %meta{ content: "text/html; charset=UTF-8", "http-equiv" => "Content-Type" }/
- %meta{ content: "width=device-width, initial-scale=1", name: "viewport" }/
- %meta{ content: "IE=edge", "http-equiv" => "X-UA-Compatible" }/
- %title= message.subject
- :css
- /* CLIENT-SPECIFIC STYLES */
- body, table, td, a { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
- table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; }
- img { -ms-interpolation-mode: bicubic; }
-
- /* iOS BLUE LINKS */
- a[x-apple-data-detectors] {
- color: inherit !important;
- text-decoration: none !important;
- font-size: inherit !important;
- font-family: inherit !important;
- font-weight: inherit !important;
- line-height: inherit !important;
- }
-
- /* ANDROID MARGIN HACK */
- body { margin:0 !important; }
- div[style*="margin: 16px 0"] { margin:0 !important; }
-
- @media only screen and (max-width: 639px) {
- body, #body {
- min-width: 320px !important;
- }
- table.wrapper {
- width: 100% !important;
- min-width: 320px !important;
- }
- table.wrapper > tbody > tr > td {
- border-left: 0 !important;
- border-right: 0 !important;
- border-radius: 0 !important;
- padding-left: 10px !important;
- padding-right: 10px !important;
- }
- }
- %body{ style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;height:100%;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" }
- %table#body{ border: "0", cellpadding: "0", cellspacing: "0", style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;" }
+%tr.success
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;color:#ffffff;background-color:#31af64;" }
+ %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;margin:0 auto;" }
%tbody
- %tr.line
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#6b4fbb;height:4px;font-size:4px;line-height:4px;" }  
- %tr.header
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
- %img{ alt: "GitLab", height: "50", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo.gif'), width: "55" }/
%tr
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" }
- %table.wrapper{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:640px;margin:0 auto;border-collapse:separate;border-spacing:0;" }
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;" }
+ %img{ alt: "✓", height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-check-green-inverted.gif'), style: "display:block;", width: "13" }/
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;" }
+ Your pipeline has passed.
+%tr.spacer
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" }
+ &nbsp;
+%tr.section
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" }
+ %table.info{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;" }
+ %tbody
+ %tr
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;" } Project
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;" }
+ - namespace_name = @project.group ? @project.group.name : @project.namespace.owner.name
+ - namespace_url = @project.group ? group_url(@project.group) : user_url(@project.namespace.owner)
+ %a.muted{ href: namespace_url, style: "color:#333333;text-decoration:none;" }
+ = namespace_name
+ \/
+ %a.muted{ href: project_url(@project), style: "color:#333333;text-decoration:none;" }
+ = @project.name
+ %tr
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Branch
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
+ %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
+ %tbody
+ %tr
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
+ %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), style: "display:block;", width: "13", alt: "Branch icon" }/
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
+ %a.muted{ href: commits_url(@pipeline), style: "color:#333333;text-decoration:none;" }
+ = @pipeline.ref
+ %tr
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Commit
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
+ %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
+ %tbody
+ %tr
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
+ %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-commit-gray.gif'), style: "display:block;", width: "13", alt: "Commit icon" }/
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
+ %a{ href: commit_url(@pipeline), style: "color:#3777b0;text-decoration:none;" }
+ = @pipeline.short_sha
+ - if @merge_request
+ in
+ %a{ href: merge_request_url(@merge_request), style: "color:#3777b0;text-decoration:none;" }
+ = @merge_request.to_reference
+ .commit{ style: "color:#5c5c5c;font-weight:300;" }
+ = @pipeline.git_commit_message.truncate(50)
+ %tr
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Author
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
+ %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
%tbody
%tr
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#ffffff;text-align:left;padding:18px 25px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" }
- %table.content{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;border-collapse:separate;border-spacing:0;" }
- %tbody
- %tr.success
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;color:#ffffff;background-color:#31af64;" }
- %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;margin:0 auto;" }
- %tbody
- %tr
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;" }
- %img{ alt: "✓", height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-check-green-inverted.gif'), style: "display:block;", width: "13" }/
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;" }
- Your pipeline has passed.
- %tr.spacer
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" }
- &nbsp;
- %tr.section
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" }
- %table.info{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;" }
- %tbody
- %tr
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;" } Project
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;" }
- - namespace_name = @project.group ? @project.group.name : @project.namespace.owner.name
- - namespace_url = @project.group ? group_url(@project.group) : user_url(@project.namespace.owner)
- %a.muted{ href: namespace_url, style: "color:#333333;text-decoration:none;" }
- = namespace_name
- \/
- %a.muted{ href: project_url(@project), style: "color:#333333;text-decoration:none;" }
- = @project.name
- %tr
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Branch
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
- %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
- %tbody
- %tr
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
- %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), style: "display:block;", width: "13", alt: "Branch icon" }/
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
- %a.muted{ href: commits_url(@pipeline), style: "color:#333333;text-decoration:none;" }
- = @pipeline.ref
- %tr
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Commit
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
- %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
- %tbody
- %tr
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
- %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-commit-gray.gif'), style: "display:block;", width: "13", alt: "Commit icon" }/
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
- %a{ href: commit_url(@pipeline), style: "color:#3777b0;text-decoration:none;" }
- = @pipeline.short_sha
- - if @merge_request
- in
- %a{ href: merge_request_url(@merge_request), style: "color:#3777b0;text-decoration:none;" }
- = @merge_request.to_reference
- .commit{ style: "color:#5c5c5c;font-weight:300;" }
- = @pipeline.git_commit_message.truncate(50)
- %tr
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Author
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
- %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
- %tbody
- %tr
- - commit = @pipeline.commit
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
- %img.avatar{ height: "24", src: avatar_icon(commit.author || commit.author_email, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
- - if commit.author
- %a.muted{ href: user_url(commit.author), style: "color:#333333;text-decoration:none;" }
- = commit.author.name
- - else
- %span
- = commit.author_name
- %tr.spacer
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" }
- &nbsp;
- %tr.success-message
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 5px;text-align:center;" }
- - build_count = @pipeline.statuses.latest.size
- - stage_count = @pipeline.stages_count
- Pipeline
- %a{ href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;" }
- = "\##{@pipeline.id}"
- successfully completed
- #{build_count} #{'build'.pluralize(build_count)}
- in
- #{stage_count} #{'stage'.pluralize(stage_count)}.
- %tr.footer
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
- %img{ alt: "GitLab", height: "33", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif'), style: "display:block;margin:0 auto 1em;", width: "90" }/
- %div
- %a{ href: profile_notifications_url, style: "color:#3777b0;text-decoration:none;" } Manage all notifications
- &middot;
- %a{ href: help_url, style: "color:#3777b0;text-decoration:none;" } Help
- %div
- You're receiving this email because of your account on
- = succeed "." do
- %a{ href: root_url, style: "color:#3777b0;text-decoration:none;" }= Gitlab.config.gitlab.host
+ - commit = @pipeline.commit
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
+ %img.avatar{ height: "24", src: avatar_icon(commit.author || commit.author_email, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
+ - if commit.author
+ %a.muted{ href: user_url(commit.author), style: "color:#333333;text-decoration:none;" }
+ = commit.author.name
+ - else
+ %span
+ = commit.author_name
+%tr.spacer
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" }
+ &nbsp;
+%tr.success-message
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 5px;text-align:center;" }
+ - build_count = @pipeline.statuses.latest.size
+ - stage_count = @pipeline.stages_count
+ Pipeline
+ %a{ href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;" }
+ = "\##{@pipeline.id}"
+ successfully completed
+ #{build_count} #{'build'.pluralize(build_count)}
+ in
+ #{stage_count} #{'stage'.pluralize(stage_count)}.
diff --git a/app/views/notify/pipeline_success_email.text.erb b/app/views/notify/pipeline_success_email.text.erb
index 40e5e306426..0970a3a4e09 100644
--- a/app/views/notify/pipeline_success_email.text.erb
+++ b/app/views/notify/pipeline_success_email.text.erb
@@ -18,7 +18,3 @@ Commit Author: <%= commit.author_name %>
<% build_count = @pipeline.statuses.latest.size -%>
<% stage_count = @pipeline.stages_count -%>
Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) successfully completed <%= build_count %> <%= 'build'.pluralize(build_count) %> in <%= stage_count %> <%= 'stage'.pluralize(stage_count) %>.
-
-You're receiving this email because of your account on <%= Gitlab.config.gitlab.host %>.
-Manage all notifications: <%= profile_notifications_url %>
-Help: <%= help_url %>
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index a4f4079d556..02fb47ec981 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -115,7 +115,7 @@
%h4.prepend-top-0.danger-title
Remove account
.col-lg-9
- - if @user.can_be_removed?
+ - if @user.can_be_removed? && can?(current_user, :destroy_user, @user)
%p
Deleting an account has the following effects:
%ul
@@ -131,4 +131,7 @@
%strong= @user.solo_owned_groups.map(&:name).join(', ')
%p
You must transfer ownership or delete these groups before you can delete your account.
+ - else
+ %p
+ You don't have access to delete this user.
.append-bottom-default
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index 5c5e5940365..51c4e8e5a73 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -34,6 +34,11 @@
.clearfix
+ = form_for @user, url: profile_notifications_path, method: :put do |f|
+ %label{ for: 'user_notified_of_own_activity' }
+ = f.check_box :notified_of_own_activity
+ %span Receive notifications about your own activity
+
%hr
%h5
Groups (#{@group_notifications.count})
diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml
index 60a561c9f9c..b10f5fc08e2 100644
--- a/app/views/profiles/personal_access_tokens/index.html.haml
+++ b/app/views/profiles/personal_access_tokens/index.html.haml
@@ -85,11 +85,17 @@
:javascript
- var date = $('#personal_access_token_expires_at').val();
-
- var datepicker = $(".datepicker").datepicker({
- dateFormat: "yy-mm-dd",
- minDate: 0
+ var $dateField = $('#personal_access_token_expires_at');
+ var date = $dateField.val();
+
+ new Pikaday({
+ field: $dateField.get(0),
+ theme: 'gitlab-theme',
+ format: 'yyyy-mm-dd',
+ minDate: new Date(),
+ onSelect: function(dateText) {
+ $dateField.val(dateFormat(new Date(dateText), 'yyyy-mm-dd'));
+ }
});
$("#created-personal-access-token").click(function() {
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index feadd863b00..df0a0212f3d 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -4,19 +4,6 @@
= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: { class: 'row prepend-top-default js-preferences-form' } do |f|
.col-lg-3.profile-settings-sidebar
%h4.prepend-top-0
- Application theme
- %p
- This setting allows you to customize the appearance of the site, e.g. the sidebar.
- .col-lg-9.application-theme
- - Gitlab::Themes.each do |theme|
- = label_tag do
- .preview{ class: theme.css_class }
- = f.radio_button :theme_id, theme.id
- = theme.name
- .col-sm-12
- %hr
- .col-lg-3.profile-settings-sidebar
- %h4.prepend-top-0
Syntax highlighting theme
%p
This setting allow you to customize the appearance of the syntax.
diff --git a/app/views/profiles/preferences/update.js.erb b/app/views/profiles/preferences/update.js.erb
index 8966dd3fd86..431ab9d052b 100644
--- a/app/views/profiles/preferences/update.js.erb
+++ b/app/views/profiles/preferences/update.js.erb
@@ -1,7 +1,3 @@
-// Remove body class for any previous theme, re-add current one
-$('body').removeClass('<%= Gitlab::Themes.body_classes %>')
-$('body').addClass('<%= user_application_theme %>')
-
// Toggle container-fluid class
if ('<%= current_user.layout %>' === 'fluid') {
$('.content-wrapper .container-fluid').removeClass('container-limited')
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 1b9d87e9969..79a0dc1b959 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -28,9 +28,11 @@
.project-clone-holder
= render "shared/clone_panel"
- - if current_user && can?(current_user, :download_code, @project)
- = render 'projects/buttons/download', project: @project, ref: @ref
- = render 'projects/buttons/dropdown'
+ - if current_user
+ - if can?(current_user, :download_code, @project)
+ = render 'projects/buttons/download', project: @project, ref: @ref
+ = render 'projects/buttons/dropdown'
+ = render 'projects/buttons/koding'
+
= render 'shared/notifications/button', notification_setting: @notification_setting
- = render 'projects/buttons/koding'
= render 'shared/members/access_request_buttons', source: @project
diff --git a/app/views/projects/_last_push.html.haml b/app/views/projects/_last_push.html.haml
index 1c3bccccb5c..a08436715d2 100644
--- a/app/views/projects/_last_push.html.haml
+++ b/app/views/projects/_last_push.html.haml
@@ -10,6 +10,7 @@
- if @project && event.project != @project
%span at
%strong= link_to_project event.project
+ = clipboard_button(clipboard_text: event.ref_name, class: 'btn-clipboard btn-transparent', title: 'Copy branch to clipboard')
#{time_ago_with_tooltip(event.created_at)}
.pull-right
diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml
index 23f54553014..8a40281e28c 100644
--- a/app/views/projects/blame/show.html.haml
+++ b/app/views/projects/blame/show.html.haml
@@ -7,7 +7,7 @@
#blob-content-holder.tree-holder
.file-holder
- .file-title
+ .js-file-title.file-title
= blob_icon @blob.mode, @blob.name
%strong
= @path
diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index f75f438ee4f..19fa4c78501 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -24,7 +24,7 @@
#blob-content-holder.blob-content-holder
%article.file-holder
- .file-title
+ .js-file-title.file-title
= blob_icon blob.mode, blob.name
%strong
= blob.name
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index 228ac61fc8c..e7adef5558a 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -1,5 +1,5 @@
.file-holder.file.append-bottom-default
- .file-title.clearfix
+ .js-file-title.file-title.clearfix
.editor-ref
= icon('code-fork')
= ref
diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml
index 3b1a2e54ec2..d1f7f65bf53 100644
--- a/app/views/projects/blob/diff.html.haml
+++ b/app/views/projects/blob/diff.html.haml
@@ -25,6 +25,6 @@
= link_to raw(line_new), "##{line_new}"
= line_content
- - if @form.unfold? && @form.bottom? && @form.to < @blob.loc
+ - if @form.unfold? && @form.bottom? && @form.to < @blob.lines.size
%tr.line_holder{ id: @form.to, class: line_class }
= diff_match_line @form.to - @form.offset, @form.to, text: @match_line, view: diff_view, bottom: true
diff --git a/app/views/projects/boards/_show.html.haml b/app/views/projects/boards/_show.html.haml
index 05fe504d1c9..f5ca9607823 100644
--- a/app/views/projects/boards/_show.html.haml
+++ b/app/views/projects/boards/_show.html.haml
@@ -4,7 +4,7 @@
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('boards')
- = page_specific_javascript_bundle_tag('boards_test') if Rails.env.test?
+ = page_specific_javascript_bundle_tag('simulate_drag') if Rails.env.test?
%script#js-board-template{ type: "text/x-template" }= render "projects/boards/components/board"
%script#js-board-list-template{ type: "text/x-template" }= render "projects/boards/components/board_list"
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 19ffe73a08d..ae63f8184df 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -31,7 +31,7 @@
- if can?(current_user, :push_code, @project)
= link_to namespace_project_branch_path(@project.namespace, @project, branch.name),
- class: "btn btn-remove remove-row #{can_remove_branch?(@project, branch.name) ? '' : 'disabled'}",
+ class: "btn btn-remove remove-row js-ajax-loading-spinner #{can_remove_branch?(@project, branch.name) ? '' : 'disabled'}",
method: :delete,
data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?" },
remote: true,
diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml
index 56fc5f5e68b..78720d88e4e 100644
--- a/app/views/projects/builds/_sidebar.html.haml
+++ b/app/views/projects/builds/_sidebar.html.haml
@@ -1,6 +1,6 @@
- builds = @build.pipeline.builds.to_a
-%aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar
+%aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar.js-right-sidebar{ data: { "offset-top" => "151", "spy" => "affix" } }
.block.build-sidebar-header.visible-xs-block.visible-sm-block.append-bottom-default
Job
%strong ##{@build.id}
diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml
index 762ff34a9ec..b560ed21f1d 100644
--- a/app/views/projects/buttons/_download.html.haml
+++ b/app/views/projects/buttons/_download.html.haml
@@ -8,19 +8,19 @@
%ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' }
%li.dropdown-header Source code
%li
- = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow' do
+ = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow', download: '' do
%i.fa.fa-download
%span Download zip
%li
- = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do
+ = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow', download: '' do
%i.fa.fa-download
%span Download tar.gz
%li
- = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do
+ = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.bz2'), rel: 'nofollow', download: '' do
%i.fa.fa-download
%span Download tar.bz2
%li
- = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar'), rel: 'nofollow' do
+ = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar'), rel: 'nofollow', download: '' do
%i.fa.fa-download
%span Download tar
@@ -36,6 +36,6 @@
%li.dropdown-header Previous Artifacts
- artifacts.each do |job|
%li
- = link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, "#{ref}/download", job: job.name), rel: 'nofollow' do
+ = link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, "#{ref}/download", job: job.name), rel: 'nofollow', download: '' do
%i.fa.fa-download
%span Download '#{job.name}'
diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml
index ac0fd87fd8d..3475fa5f960 100644
--- a/app/views/projects/ci/pipelines/_pipeline.html.haml
+++ b/app/views/projects/ci/pipelines/_pipeline.html.haml
@@ -15,7 +15,7 @@
- else
%span.api.monospace API
- if pipeline.latest?
- %span.label.label-success.has-tooltip{ title: 'Latest job for this branch' } latest
+ %span.label.label-success.has-tooltip{ title: 'Latest pipeline for this branch' } latest
- if pipeline.triggered?
%span.label.label-primary triggered
- if pipeline.yaml_errors.present?
@@ -61,7 +61,7 @@
.btn-group.inline
- if actions.any?
.btn-group
- %button.dropdown-toggle.btn.btn-default.has-tooltip.js-pipeline-dropdown-manual-actions{ type: 'button', title: 'Manual job', data: { toggle: 'dropdown', placement: 'top' }, 'aria-label' => 'Manual job' }
+ %button.dropdown-toggle.btn.btn-default.has-tooltip.js-pipeline-dropdown-manual-actions{ type: 'button', title: 'Manual pipeline', data: { toggle: 'dropdown', placement: 'top' }, 'aria-label' => 'Manual pipeline' }
= custom_icon('icon_play')
= icon('caret-down', 'aria-hidden' => 'true')
%ul.dropdown-menu.dropdown-menu-align-right
@@ -78,7 +78,7 @@
%ul.dropdown-menu.dropdown-menu-align-right
- artifacts.each do |build|
%li
- = link_to download_namespace_project_build_artifacts_path(pipeline.project.namespace, pipeline.project, build), rel: 'nofollow' do
+ = link_to download_namespace_project_build_artifacts_path(pipeline.project.namespace, pipeline.project, build), rel: 'nofollow', download: '' do
= icon("download")
%span Download '#{build.name}' artifacts
diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml
index 6abff6aaf95..c2b32a22170 100644
--- a/app/views/projects/commit/_pipeline.html.haml
+++ b/app/views/projects/commit/_pipeline.html.haml
@@ -3,7 +3,7 @@
.pull-right
- if can?(current_user, :update_pipeline, pipeline.project)
- if pipeline.builds.latest.failed.any?(&:retryable?)
- = link_to "Retry failed", retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn btn-grouped btn-primary', method: :post
+ = link_to "Retry", retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'js-retry-button btn btn-grouped btn-primary', method: :post
- if pipeline.builds.running_or_pending.any?
= link_to "Cancel running", cancel_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post
diff --git a/app/views/projects/commit/_pipelines_list.haml b/app/views/projects/commit/_pipelines_list.haml
index aae2cb8a04b..33917513f37 100644
--- a/app/views/projects/commit/_pipelines_list.haml
+++ b/app/views/projects/commit/_pipelines_list.haml
@@ -1,4 +1,7 @@
-#commit-pipeline-table-view{ data: { endpoint: endpoint } }
+- disable_initialization = local_assigns.fetch(:disable_initialization, false)
+#commit-pipeline-table-view{ data: { disable_initialization: disable_initialization,
+ endpoint: endpoint,
+} }
.pipeline-svgs{ data: { "commit_icon_svg" => custom_icon("icon_commit"),
"icon_status_canceled" => custom_icon("icon_status_canceled"),
"icon_status_running" => custom_icon("icon_status_running"),
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index d94f23f5a38..08cb8a04413 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -22,9 +22,7 @@
= link_to "View Open Merge Request", namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn'
- elsif create_mr_button?(@repository.root_ref, @ref)
.control
- = link_to create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success' do
- = icon('plus')
- Create Merge Request
+ = link_to "Create Merge Request", create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success'
.control
= form_tag(namespace_project_commits_path(@project.namespace, @project, @id), method: :get, class: 'commits-search-form') do
diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml
index d76d48187cd..08236216421 100644
--- a/app/views/projects/compare/_form.html.haml
+++ b/app/views/projects/compare/_form.html.haml
@@ -23,6 +23,4 @@
- if @merge_request.present?
= link_to "View Open Merge Request", namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'prepend-left-10 btn'
- elsif create_mr_button?
- = link_to create_mr_path, class: 'prepend-left-10 btn' do
- = icon("plus")
- Create Merge Request
+ = link_to "Create Merge Request", create_mr_path, class: 'prepend-left-10 btn'
diff --git a/app/views/projects/diffs/_content.html.haml b/app/views/projects/diffs/_content.html.haml
index b87b79b170e..5c38b5ad9c0 100644
--- a/app/views/projects/diffs/_content.html.haml
+++ b/app/views/projects/diffs/_content.html.haml
@@ -15,10 +15,13 @@
%a.click-to-expand
Click to expand it.
- elsif diff_file.diff_lines.length > 0
+ - total_lines = 0
+ - if blob.lines.any?
+ - total_lines = blob.lines.last.chomp == '' ? blob.lines.size - 1 : blob.lines.size
- if diff_view == :parallel
- = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob
+ = render "projects/diffs/parallel_view", diff_file: diff_file, total_lines: total_lines
- else
- = render "projects/diffs/text_file", diff_file: diff_file
+ = render "projects/diffs/text_file", diff_file: diff_file, total_lines: total_lines
- else
- if diff_file.mode_changed?
.nothing-here-block File mode changed
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index 75885badac9..0232a09b4a8 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -1,7 +1,8 @@
- environment = local_assigns.fetch(:environment, nil)
.diff-file.file-holder{ id: file_hash, data: diff_file_html_data(project, diff_file.file_path, diff_commit.id) }
- .file-title
- = render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_commit, project: project, url: "##{file_hash}"
+ .js-file-title.file-title-flex-parent
+ .file-header-content
+ = render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_commit, project: project, url: "##{file_hash}"
- unless diff_file.submodule?
.file-actions.hidden-xs
diff --git a/app/views/projects/diffs/_file_header.html.haml b/app/views/projects/diffs/_file_header.html.haml
index ddec775b789..1dbfe830d52 100644
--- a/app/views/projects/diffs/_file_header.html.haml
+++ b/app/views/projects/diffs/_file_header.html.haml
@@ -10,13 +10,13 @@
- if diff_file.renamed_file
- old_path, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path)
- %strong
+ %strong.file-title-name.has-tooltip{ data: { title: old_path, container: 'body' } }
= old_path
&rarr;
- %strong
+ %strong.file-title-name.has-tooltip{ data: { title: new_path, container: 'body' } }
= new_path
- else
- %strong
+ %strong.file-title-name.has-tooltip{ data: { title: diff_file.new_path, container: 'body' } }
= diff_file.new_path
- if diff_file.deleted_file
deleted
diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml
index 074f1f634ae..997bf0fc560 100644
--- a/app/views/projects/diffs/_parallel_view.html.haml
+++ b/app/views/projects/diffs/_parallel_view.html.haml
@@ -43,7 +43,8 @@
- discussion_left, discussion_right = parallel_diff_discussions(left, right, diff_file)
- if discussion_left || discussion_right
= render "discussions/parallel_diff_discussion", discussion_left: discussion_left, discussion_right: discussion_right
- - if !diff_file.new_file && diff_file.diff_lines.any?
+ - if !diff_file.new_file && !diff_file.deleted_file && diff_file.diff_lines.any?
- last_line = diff_file.diff_lines.last
- %tr.line_holder.parallel
- = diff_match_line last_line.old_pos, last_line.new_pos, bottom: true, view: :parallel
+ - if last_line.new_pos < total_lines
+ %tr.line_holder.parallel
+ = diff_match_line last_line.old_pos, last_line.new_pos, bottom: true, view: :parallel
diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml
index 2eea1db169a..ebd1a914ee7 100644
--- a/app/views/projects/diffs/_text_file.html.haml
+++ b/app/views/projects/diffs/_text_file.html.haml
@@ -10,7 +10,8 @@
as: :line,
locals: { diff_file: diff_file, discussions: discussions }
- - if !diff_file.new_file && diff_file.highlighted_diff_lines.any?
+ - if !diff_file.new_file && !diff_file.deleted_file && diff_file.highlighted_diff_lines.any?
- last_line = diff_file.highlighted_diff_lines.last
- %tr.line_holder
- = diff_match_line last_line.old_pos, last_line.new_pos, bottom: true
+ - if last_line.new_pos < total_lines
+ %tr.line_holder
+ = diff_match_line last_line.old_pos, last_line.new_pos, bottom: true
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 9c5c1a6d707..83ae9fd10ec 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -6,7 +6,7 @@
.col-lg-9
.project-edit-errors
= form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit-project" }, authenticity_token: true do |f|
- %fieldset.append-bottom-0
+ %fieldset
.row
.form-group.col-md-9
= f.label :name, class: 'label-light', for: 'project_name_edit' do
@@ -33,7 +33,7 @@
= f.text_field :tag_list, value: @project.tag_list.to_s, maxlength: 2000, class: "form-control"
%p.help-block Separate tags with commas.
%hr
- %fieldset.append-bottom-0
+ %fieldset
%h5.prepend-top-0
Sharing &amp; Permissions
.form_group.prepend-top-20.sharing-and-permissions
@@ -120,7 +120,7 @@
.form-group
- if @project.avatar?
.avatar-container.s160
- = project_icon("#{@project.namespace.to_param}/#{@project.to_param}", alt: '', class: 'avatar project-avatar s160')
+ = project_icon(@project.full_path, alt: '', class: 'avatar project-avatar s160')
%p.light
- if @project.avatar_in_git
Project avatar in repository: #{ @project.avatar_in_git }
@@ -232,7 +232,7 @@
.form-group
.input-group
.input-group-addon
- #{URI.join(root_url, @project.namespace.path)}/
+ #{URI.join(root_url, @project.namespace.full_path)}/
= f.text_field :path, class: 'form-control'
%ul
%li Be careful. Renaming a project's repository can have unintended side effects.
diff --git a/app/views/projects/environments/folder.html.haml b/app/views/projects/environments/folder.html.haml
new file mode 100644
index 00000000000..d9cb7bc0331
--- /dev/null
+++ b/app/views/projects/environments/folder.html.haml
@@ -0,0 +1,13 @@
+- @no_container = true
+- page_title "Environments"
+= render "projects/pipelines/head"
+
+- content_for :page_specific_javascripts do
+ = page_specific_javascript_bundle_tag("environments_folder")
+
+#environments-folder-list-view{ data: { "can-create-deployment" => can?(current_user, :create_deployment, @project).to_s,
+ "can-read-environment" => can?(current_user, :read_environment, @project).to_s,
+ "css-class" => container_class,
+ "commit-icon-svg" => custom_icon("icon_commit"),
+ "terminal-icon-svg" => custom_icon("icon_terminal"),
+ "play-icon-svg" => custom_icon("icon_play") } }
diff --git a/app/views/projects/environments/terminal.html.haml b/app/views/projects/environments/terminal.html.haml
index 1d49e9cbaf7..ef0dd0eda3c 100644
--- a/app/views/projects/environments/terminal.html.haml
+++ b/app/views/projects/environments/terminal.html.haml
@@ -16,6 +16,8 @@
.col-sm-6
.nav-controls
+ = link_to @environment.external_url, class: 'btn btn-default' do
+ = icon('external-link')
= render 'projects/deployments/actions', deployment: @environment.last_deployment
.terminal-container{ class: container_class }
diff --git a/app/views/projects/group_links/_index.html.haml b/app/views/projects/group_links/_index.html.haml
index 99d0df2ac34..b6116dbec41 100644
--- a/app/views/projects/group_links/_index.html.haml
+++ b/app/views/projects/group_links/_index.html.haml
@@ -39,7 +39,7 @@
= icon("folder-open-o", class: "settings-list-icon")
.pull-left
= link_to group do
- = group.name
+ = group.full_name
%br
up to #{group_link.human_access}
- if group_link.expires?
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
index c2f4457b60b..5d4e593e4ef 100644
--- a/app/views/projects/issues/_discussion.html.haml
+++ b/app/views/projects/issues/_discussion.html.haml
@@ -1,7 +1,7 @@
- content_for :note_actions do
- if can?(current_user, :update_issue, @issue)
- = link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true, original_text: "Reopen issue", alternative_text: "Comment & reopen issue"}, class: "btn btn-nr btn-reopen btn-comment js-note-target-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
- = link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true, original_text: "Close issue", alternative_text: "Comment & close issue"}, class: "btn btn-nr btn-close btn-comment js-note-target-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
+ = link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, format: 'json'), data: {no_turbolink: true, original_text: "Reopen issue", alternative_text: "Comment & reopen issue"}, class: "btn btn-nr btn-reopen btn-comment js-note-target-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
+ = link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, format: 'json'), data: {no_turbolink: true, original_text: "Close issue", alternative_text: "Comment & close issue"}, class: "btn btn-nr btn-close btn-comment js-note-target-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
#notes
= render 'projects/notes/notes_with_form'
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 085b2fc2814..0e3902c066a 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -1,60 +1,46 @@
%li{ id: dom_id(issue), class: issue_css_classes(issue), url: issue_path(issue), data: { labels: issue.label_ids, id: issue.id } }
- - if @bulk_edit
- .issue-check
- = check_box_tag dom_id(issue, "selected"), nil, false, 'data-id' => issue.id, class: "selected_issue"
+ .issue-box
+ - if @bulk_edit
+ .issue-check
+ = check_box_tag dom_id(issue, "selected"), nil, false, 'data-id' => issue.id, class: "selected_issue"
+ .issue-info-container
+ .issue-title.title
+ %span.issue-title-text
+ = confidential_icon(issue)
+ = link_to issue.title, issue_path(issue)
+ %ul.controls
+ - if issue.closed?
+ %li
+ CLOSED
- .issue-title.title
- %span.issue-title-text
- = confidential_icon(issue)
- = link_to issue.title, issue_path(issue)
- %ul.controls
- - if issue.closed?
- %li
- CLOSED
+ - if issue.assignee
+ %li
+ = link_to_member(@project, issue.assignee, name: false, title: "Assigned to :name")
- - if issue.assignee
- %li
- = link_to_member(@project, issue.assignee, name: false, title: "Assigned to :name")
+ = render 'shared/issuable_meta_data', issuable: issue
- - upvotes, downvotes = issue.upvotes, issue.downvotes
- - if upvotes > 0
- %li
- = icon('thumbs-up')
- = upvotes
+ .issue-info
+ #{issuable_reference(issue)} &middot;
+ opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')}
+ by #{link_to_member(@project, issue.author, avatar: false)}
+ - if issue.milestone
+ &nbsp;
+ = link_to namespace_project_issues_path(issue.project.namespace, issue.project, milestone_title: issue.milestone.title) do
+ = icon('clock-o')
+ = issue.milestone.title
+ - if issue.due_date
+ %span{ class: "#{'cred' if issue.overdue?}" }
+ &nbsp;
+ = icon('calendar')
+ = issue.due_date.to_s(:medium)
+ - if issue.labels.any?
+ &nbsp;
+ - issue.labels.each do |label|
+ = link_to_label(label, subject: issue.project, css_class: 'label-link')
+ - if issue.tasks?
+ &nbsp;
+ %span.task-status
+ = issue.task_status
- - if downvotes > 0
- %li
- = icon('thumbs-down')
- = downvotes
-
- - note_count = issue.notes.user.count
- %li
- = link_to issue_path(issue, anchor: 'notes'), class: ('no-comments' if note_count.zero?) do
- = icon('comments')
- = note_count
-
- .issue-info
- #{issuable_reference(issue)} &middot;
- opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')}
- by #{link_to_member(@project, issue.author, avatar: false)}
- - if issue.milestone
- &nbsp;
- = link_to namespace_project_issues_path(issue.project.namespace, issue.project, milestone_title: issue.milestone.title) do
- = icon('clock-o')
- = issue.milestone.title
- - if issue.due_date
- %span{ class: "#{'cred' if issue.overdue?}" }
- &nbsp;
- = icon('calendar')
- = issue.due_date.to_s(:medium)
- - if issue.labels.any?
- &nbsp;
- - issue.labels.each do |label|
- = link_to_label(label, subject: issue.project, css_class: 'label-link')
- - if issue.tasks?
- &nbsp;
- %span.task-status
- = issue.task_status
-
- .pull-right.issue-updated-at
- %span updated #{time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_update_ago')}
+ .pull-right.issue-updated-at
+ %span updated #{time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_update_ago')}
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index a2305f4f547..069f3d97943 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -35,12 +35,12 @@
= link_to 'New issue', new_namespace_project_issue_path(@project.namespace, @project), title: 'New issue', id: 'new_issue_link'
- if can?(current_user, :update_issue, @issue)
%li
- = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
+ = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), data: {no_turbolink: true}, class: "btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
%li
- = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
+ = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), data: {no_turbolink: true}, class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
%li
= link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue)
- - if @issue.submittable_as_spam? && current_user.admin?
+ - if @issue.submittable_as_spam_by?(current_user)
%li
= link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'btn-spam', title: 'Submit as spam'
@@ -48,9 +48,9 @@
= link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do
New issue
- if can?(current_user, :update_issue, @issue)
- = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
- = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
- - if @issue.submittable_as_spam? && current_user.admin?
+ = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
+ = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
+ - if @issue.submittable_as_spam_by?(current_user)
= link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'hidden-xs hidden-sm btn btn-grouped btn-spam', title: 'Submit as spam'
= link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped issuable-edit'
diff --git a/app/views/projects/issues/verify.html.haml b/app/views/projects/issues/verify.html.haml
index 1934b18c086..09aa401e44a 100644
--- a/app/views/projects/issues/verify.html.haml
+++ b/app/views/projects/issues/verify.html.haml
@@ -1,20 +1,4 @@
-- page_title "Anti-spam verification"
+- form = [@project.namespace.becomes(Namespace), @project, @issue]
-%h3.page-title
- Anti-spam verification
-%hr
-
-%p
- We detected potential spam in the issue description. Please verify that you are not a robot to submit the issue.
-
-= form_for [@project.namespace.becomes(Namespace), @project, @issue] do |f|
- .recaptcha
- - params[:issue].each do |field, value|
- = hidden_field(:issue, field, value: value)
- = hidden_field_tag(:merge_request_for_resolving_discussions, params[:merge_request_for_resolving_discussions])
- = hidden_field_tag(:spam_log_id, @issue.spam_log.id)
- = hidden_field_tag(:recaptcha_verification, true)
- = recaptcha_tags
-
- .row-content-block.footer-block
- = f.submit "Submit #{@issue.class.model_name.human.downcase}", class: 'btn btn-create'
+= render layout: 'layouts/recaptcha_verification', locals: { spammable: @issue, form: form } do
+ = hidden_field_tag(:merge_request_for_resolving_discussions, params[:merge_request_for_resolving_discussions])
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index 29f861c09c6..8d4a91cb64c 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -3,6 +3,9 @@
- hide_class = ''
= render "projects/issues/head"
+- content_for :page_specific_javascripts do
+ = page_specific_javascript_bundle_tag('simulate_drag') if Rails.env.test?
+
- if @labels.exists? || @prioritized_labels.exists?
%div{ class: container_class }
.top-area.adjust
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 4dbb97b3228..11b7aaec704 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -3,73 +3,59 @@
.issue-check
= check_box_tag dom_id(merge_request, "selected"), nil, false, 'data-id' => merge_request.id, class: "selected_issue"
- .merge-request-title.title
- %span.merge-request-title-text
- = link_to merge_request.title, merge_request_path(merge_request)
- %ul.controls
- - if merge_request.merged?
- %li
- MERGED
- - elsif merge_request.closed?
- %li
- = icon('ban')
- CLOSED
-
- - if merge_request.head_pipeline
- %li
- = render_pipeline_status(merge_request.head_pipeline)
-
- - if merge_request.open? && merge_request.broken?
- %li
- = link_to merge_request_path(merge_request), class: "has-tooltip", title: "Cannot be merged automatically", data: { container: 'body' } do
- = icon('exclamation-triangle')
-
- - if merge_request.assignee
- %li
- = link_to_member(merge_request.source_project, merge_request.assignee, name: false, title: "Assigned to :name")
-
- - upvotes, downvotes = merge_request.upvotes, merge_request.downvotes
- - if upvotes > 0
- %li
- = icon('thumbs-up')
- = upvotes
-
- - if downvotes > 0
- %li
- = icon('thumbs-down')
- = downvotes
-
- - note_count = merge_request.related_notes.user.count
- %li
- = link_to merge_request_path(merge_request, anchor: 'notes'), class: ('no-comments' if note_count.zero?) do
- = icon('comments')
- = note_count
-
- .merge-request-info
- #{issuable_reference(merge_request)} &middot;
- opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')}
- by #{link_to_member(@project, merge_request.author, avatar: false)}
- - if merge_request.target_project.default_branch != merge_request.target_branch
- &nbsp;
- = link_to namespace_project_commits_path(merge_request.project.namespace, merge_request.project, merge_request.target_branch) do
- = icon('code-fork')
- = merge_request.target_branch
-
- - if merge_request.milestone
- &nbsp;
- = link_to namespace_project_merge_requests_path(merge_request.project.namespace, merge_request.project, milestone_title: merge_request.milestone.title) do
- = icon('clock-o')
- = merge_request.milestone.title
-
- - if merge_request.labels.any?
- &nbsp;
- - merge_request.labels.each do |label|
- = link_to_label(label, subject: merge_request.project, type: :merge_request, css_class: 'label-link')
-
- - if merge_request.tasks?
- &nbsp;
- %span.task-status
- = merge_request.task_status
-
- .pull-right.hidden-xs
- %span updated #{time_ago_with_tooltip(merge_request.updated_at, placement: 'bottom', html_class: 'merge_request_updated_ago')}
+ .issue-info-container
+ .merge-request-title.title
+ %span.merge-request-title-text
+ = link_to merge_request.title, merge_request_path(merge_request)
+ %ul.controls
+ - if merge_request.merged?
+ %li
+ MERGED
+ - elsif merge_request.closed?
+ %li
+ = icon('ban')
+ CLOSED
+
+ - if merge_request.head_pipeline
+ %li
+ = render_pipeline_status(merge_request.head_pipeline)
+
+ - if merge_request.open? && merge_request.broken?
+ %li
+ = link_to merge_request_path(merge_request), class: "has-tooltip", title: "Cannot be merged automatically", data: { container: 'body' } do
+ = icon('exclamation-triangle')
+
+ - if merge_request.assignee
+ %li
+ = link_to_member(merge_request.source_project, merge_request.assignee, name: false, title: "Assigned to :name")
+
+ = render 'shared/issuable_meta_data', issuable: merge_request
+
+ .merge-request-info
+ #{issuable_reference(merge_request)} &middot;
+ opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')}
+ by #{link_to_member(@project, merge_request.author, avatar: false)}
+ - if merge_request.target_project.default_branch != merge_request.target_branch
+ &nbsp;
+ = link_to namespace_project_commits_path(merge_request.project.namespace, merge_request.project, merge_request.target_branch) do
+ = icon('code-fork')
+ = merge_request.target_branch
+
+ - if merge_request.milestone
+ &nbsp;
+ = link_to namespace_project_merge_requests_path(merge_request.project.namespace, merge_request.project, milestone_title: merge_request.milestone.title) do
+ = icon('clock-o')
+ = merge_request.milestone.title
+
+ - if merge_request.labels.any?
+ &nbsp;
+ - merge_request.labels.each do |label|
+ = link_to_label(label, subject: merge_request.project, type: :merge_request, css_class: 'label-link')
+
+ - if merge_request.tasks?
+ &nbsp;
+ %span.task-status
+ = merge_request.task_status
+
+ .pull-right.hidden-xs
+ %span updated #{time_ago_with_tooltip(merge_request.updated_at, placement: 'bottom', html_class: 'merge_request_updated_ago')}
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 83250443bea..521b0694ca9 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -3,10 +3,9 @@
- page_description @merge_request.description
- page_card_attributes @merge_request.card_attributes
- content_for :page_specific_javascripts do
- = page_specific_javascript_bundle_tag('lib_vue')
= page_specific_javascript_bundle_tag('diff_notes')
-.merge-request{ 'data-url' => merge_request_path(@merge_request) }
+.merge-request{ 'data-url' => merge_request_path(@merge_request), 'data-project-path' => project_path(@merge_request.project) }
= render "projects/merge_requests/show/mr_title"
.merge-request-details.issuable-details{ data: { id: @merge_request.project.id } }
@@ -95,7 +94,7 @@
-# This tab is always loaded via AJAX
#pipelines.pipelines.tab-pane
- if @pipelines.any?
- = render 'projects/commit/pipelines_list', endpoint: pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
+ = render 'projects/commit/pipelines_list', disable_initialization: true, endpoint: pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
#diffs.diffs.tab-pane
-# This tab is always loaded via AJAX
diff --git a/app/views/projects/merge_requests/conflicts.html.haml b/app/views/projects/merge_requests/conflicts.html.haml
index dcf578b85f9..1ecd9924d88 100644
--- a/app/views/projects/merge_requests/conflicts.html.haml
+++ b/app/views/projects/merge_requests/conflicts.html.haml
@@ -23,7 +23,7 @@
.files-wrapper{ "v-if" => "!isLoading && !hasError" }
.files
.diff-file.file-holder.conflict{ "v-for" => "file in conflictsData.files" }
- .file-title
+ .js-file-title.file-title
%i.fa.fa-fw{ ":class" => "file.iconClass" }
%strong {{file.filePath}}
= render partial: 'projects/merge_requests/conflicts/file_actions'
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index 144b3a9c8c8..83e6c026ba7 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -5,18 +5,19 @@
= render "projects/issues/head"
= render 'projects/last_push'
+- content_for :page_specific_javascripts do
+ = page_specific_javascript_bundle_tag('filtered_search')
+
%div{ class: container_class }
.top-area
= render 'shared/issuable/nav', type: :merge_requests
.nav-controls
- = render 'shared/issuable/search_form', path: namespace_project_merge_requests_path(@project.namespace, @project)
-
- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
- if merge_project
= link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project), class: "btn btn-new", title: "New Merge Request" do
New Merge Request
- = render 'shared/issuable/filter', type: :merge_requests
+ = render 'shared/issuable/search_bar', type: :merge_requests
.merge-requests-holder
= render 'merge_requests'
diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml
index bef76f16ca7..c676953f729 100644
--- a/app/views/projects/merge_requests/widget/_heading.html.haml
+++ b/app/views/projects/merge_requests/widget/_heading.html.haml
@@ -9,8 +9,9 @@
Pipeline
= link_to "##{@pipeline.id}", namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), class: 'pipeline'
= ci_label_for_status(status)
- .mr-widget-pipeline-graph
- = render 'shared/mini_pipeline_graph', pipeline: @pipeline, klass: 'js-pipeline-inline-mr-widget-graph'
+ - if @pipeline.stages.any?
+ .mr-widget-pipeline-graph
+ = render 'shared/mini_pipeline_graph', pipeline: @pipeline, klass: 'js-pipeline-inline-mr-widget-graph'
%span
for
= succeed "." do
@@ -27,7 +28,7 @@
%span
CI job
= ci_label_for_status(status)
- for
+ for
- commit = @merge_request.diff_head_commit
= succeed "." do
= link_to commit.short_id, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, commit), class: "monospace"
diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml
index 5de59473840..0b0fb7854c2 100644
--- a/app/views/projects/merge_requests/widget/_show.html.haml
+++ b/app/views/projects/merge_requests/widget/_show.html.haml
@@ -16,13 +16,13 @@
gitlab_icon: "#{asset_path 'gitlab_logo.png'}",
ci_status: "#{@merge_request.head_pipeline ? @merge_request.head_pipeline.status : ''}",
ci_message: {
- normal: "Job {{status}} for \"{{title}}\"",
- preparing: "{{status}} job for \"{{title}}\""
+ normal: "Pipeline {{status}} for \"{{title}}\"",
+ preparing: "{{status}} pipeline for \"{{title}}\""
},
ci_enable: #{@project.ci_service ? "true" : "false"},
ci_title: {
- preparing: "{{status}} job",
- normal: "Job {{status}}"
+ preparing: "{{status}} pipeline",
+ normal: "Pipeline {{status}}"
},
ci_sha: "#{@merge_request.head_pipeline ? @merge_request.head_pipeline.short_sha : ''}",
ci_pipeline: #{@merge_request.head_pipeline.try(:id).to_json},
diff --git a/app/views/projects/merge_requests/widget/open/_build_failed.html.haml b/app/views/projects/merge_requests/widget/open/_build_failed.html.haml
index a18c2ad768f..3979d5fa8ed 100644
--- a/app/views/projects/merge_requests/widget/open/_build_failed.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_build_failed.html.haml
@@ -1,6 +1,6 @@
%h4
= icon('exclamation-triangle')
- The job for this merge request failed
+ The pipeline for this merge request failed
%p
Please retry the job or push a new commit to fix the failure.
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index c3a6096aa54..06a31698ee6 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -3,6 +3,9 @@
- page_description @milestone.description
= render "projects/issues/head"
+- content_for :page_specific_javascripts do
+ = page_specific_javascript_bundle_tag('simulate_drag') if Rails.env.test?
+
%div{ class: container_class }
.detail-page-header.milestone-page-header
.status-box{ class: status_box_class(@milestone) }
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index cd685f7d0eb..2a98bba05ee 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -12,7 +12,7 @@
Create or Import your project from popular Git services
.col-lg-9
= form_for @project, html: { class: 'new_project' } do |f|
- %fieldset.append-bottom-0
+ .row
.form-group.col-xs-12.col-sm-6
= f.label :namespace_id, class: 'label-light' do
%span
@@ -22,7 +22,7 @@
- if current_user.can_select_namespace?
.input-group-addon
= root_url
- = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user, display_path: true), {}, {class: 'select2 js-select-namespace', tabindex: 1}
+ = f.select :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true), {}, {class: 'select2 js-select-namespace', tabindex: 1}
- else
.input-group-addon.static-namespace
@@ -94,9 +94,8 @@
.form-group.project-visibility-level-holder
= f.label :visibility_level, class: 'label-light' do
Visibility Level
- = link_to "(?)", help_page_path("public_access/public_access")
- = render 'shared/visibility_level', f: f, visibility_level: default_project_visibility, can_change_visibility_level: true, form_model: @project
-
+ = link_to icon('question-circle'), help_page_path("public_access/public_access")
+ = render 'shared/visibility_level', f: f, visibility_level: default_project_visibility, can_change_visibility_level: true, form_model: @project, with_label: false
= f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4
= link_to 'Cancel', dashboard_projects_path, class: 'btn btn-cancel'
diff --git a/app/views/projects/notes/_hints.html.haml b/app/views/projects/notes/_hints.html.haml
index 6c14f48d41b..81d97eabe65 100644
--- a/app/views/projects/notes/_hints.html.haml
+++ b/app/views/projects/notes/_hints.html.haml
@@ -1,7 +1,6 @@
- supports_slash_commands = local_assigns.fetch(:supports_slash_commands, false)
.comment-toolbar.clearfix
.toolbar-text
- Styling with
= link_to 'Markdown', help_page_path('user/markdown'), target: '_blank', tabindex: -1
- if supports_slash_commands
and
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 4b1da9c73e5..a73e8f345e0 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -30,8 +30,7 @@
- if note.resolvable?
- can_resolve = can?(current_user, :resolve_note, note)
- %resolve-btn{ "project-path" => "#{project_path(note.project)}",
- "discussion-id" => "#{note.discussion_id}",
+ %resolve-btn{ "discussion-id" => "#{note.discussion_id}",
":note-id" => note.id,
":resolved" => note.resolved?,
":can-resolve" => can_resolve,
@@ -72,12 +71,13 @@
- if note_editable
.original-note-content.hidden{ data: { post_url: namespace_project_note_path(@project.namespace, @project, note), target_id: note.noteable.id, target_type: note.noteable.class.name.underscore } }
#{note.note}
- %textarea.hidden.js-task-list-field.original-task-list= note.note
+ %textarea.hidden.js-task-list-field.original-task-list{ data: {update_url: namespace_project_note_path(@project.namespace, @project, note) } }= note.note
.note-awards
= render 'award_emoji/awards_block', awardable: note, inline: false
- if note.system
.system-note-commit-list-toggler
Toggle commit list
+ %i.fa.fa-angle-down
- if note.attachment.url
.note-attachment
- if note.attachment.image?
diff --git a/app/views/projects/pages_domains/show.html.haml b/app/views/projects/pages_domains/show.html.haml
index 52dddb052a7..876cac0dacb 100644
--- a/app/views/projects/pages_domains/show.html.haml
+++ b/app/views/projects/pages_domains/show.html.haml
@@ -17,7 +17,7 @@
%p
To access the domain create a new DNS record:
%pre
- #{@domain.domain} CNAME #{@domain.project.namespace.path}.#{Settings.pages.host}.
+ #{@domain.domain} CNAME #{@domain.project.pages_subdomain}.#{Settings.pages.host}.
%tr
%td
Certificate
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index a6cd2d83bd5..0605af4fcd3 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -7,9 +7,9 @@
= commit_author_link(@commit)
.header-action-buttons
- if can?(current_user, :update_pipeline, @pipeline.project)
- - if @pipeline.builds.latest.failed.any?(&:retryable?)
- = link_to "Retry failed", retry_namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), class: 'btn btn-inverted-secondary', method: :post
- - if @pipeline.builds.running_or_pending.any?
+ - if @pipeline.retryable?
+ = link_to "Retry", retry_namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), class: 'js-retry-button btn btn-inverted-secondary', method: :post
+ - if @pipeline.cancelable?
= link_to "Cancel running", cancel_namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
- if @commit
diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml
index 81e393d7626..4147a617d95 100644
--- a/app/views/projects/pipelines/index.html.haml
+++ b/app/views/projects/pipelines/index.html.haml
@@ -5,23 +5,35 @@
%div{ class: container_class }
.top-area
%ul.nav-links
- %li{ class: ('active' if @scope.nil?) }>
+ %li.js-pipelines-tab-all{ class: active_when(@scope.nil?) }>
= link_to project_pipelines_path(@project) do
All
%span.badge.js-totalbuilds-count
= number_with_delimiter(@pipelines_count)
- %li{ class: ('active' if @scope == 'running') }>
+ %li.js-pipelines-tab-pending{ class: active_when(@scope == 'pending') }>
+ = link_to project_pipelines_path(@project, scope: :pending) do
+ Pending
+ %span.badge
+ = number_with_delimiter(@pending_count)
+
+ %li.js-pipelines-tab-running{ class: active_when(@scope == 'running') }>
= link_to project_pipelines_path(@project, scope: :running) do
Running
%span.badge.js-running-count
- = number_with_delimiter(@running_or_pending_count)
+ = number_with_delimiter(@running_count)
+
+ %li.js-pipelines-tab-finished{ class: active_when(@scope == 'finished') }>
+ = link_to project_pipelines_path(@project, scope: :finished) do
+ Finished
+ %span.badge
+ = number_with_delimiter(@finished_count)
- %li{ class: ('active' if @scope == 'branches') }>
+ %li.js-pipelines-tab-branches{ class: active_when(@scope == 'branches') }>
= link_to project_pipelines_path(@project, scope: :branches) do
Branches
- %li{ class: ('active' if @scope == 'tags') }>
+ %li.js-pipelines-tab-tags{ class: active_when(@scope == 'tags') }>
= link_to project_pipelines_path(@project, scope: :tags) do
Tags
diff --git a/app/views/projects/pipelines_settings/_badge.html.haml b/app/views/projects/pipelines_settings/_badge.html.haml
index 22a3b884520..43bbd735059 100644
--- a/app/views/projects/pipelines_settings/_badge.html.haml
+++ b/app/views/projects/pipelines_settings/_badge.html.haml
@@ -25,3 +25,10 @@
HTML
.col-md-10.code.js-syntax-highlight
= highlight('.html', badge.to_html)
+ .row
+ %hr
+ .row
+ .col-md-2.text-center
+ AsciiDoc
+ .col-md-10.code.js-syntax-highlight
+ = highlight('.adoc', badge.to_asciidoc)
diff --git a/app/views/projects/pipelines_settings/_show.html.haml b/app/views/projects/pipelines_settings/_show.html.haml
index 8024fb8979d..132f6372e40 100644
--- a/app/views/projects/pipelines_settings/_show.html.haml
+++ b/app/views/projects/pipelines_settings/_show.html.haml
@@ -60,7 +60,7 @@
= f.label :build_coverage_regex, "Test coverage parsing", class: 'label-light'
.input-group
%span.input-group-addon /
- = f.text_field :build_coverage_regex, class: 'form-control', placeholder: '\(\d+.\d+\%\) covered'
+ = f.text_field :build_coverage_regex, class: 'form-control', placeholder: 'Regular expression'
%span.input-group-addon /
%p.help-block
A regular expression that will be used to find the test coverage
diff --git a/app/views/projects/runners/_form.html.haml b/app/views/projects/runners/_form.html.haml
index 98e72f6c547..2ef1f98ba48 100644
--- a/app/views/projects/runners/_form.html.haml
+++ b/app/views/projects/runners/_form.html.haml
@@ -32,7 +32,7 @@
= label_tag :tag_list, class: 'control-label' do
Tags
.col-sm-10
- = f.text_field :tag_list, value: runner.tag_list.to_s, class: 'form-control'
+ = f.text_field :tag_list, value: runner.tag_list.sort.join(', '), class: 'form-control'
.help-block You can setup jobs to only use Runners with specific tags
.form-actions
= f.submit 'Save changes', class: 'btn btn-save'
diff --git a/app/views/projects/runners/_runner.html.haml b/app/views/projects/runners/_runner.html.haml
index 7036b8a5ccc..deeadb609f6 100644
--- a/app/views/projects/runners/_runner.html.haml
+++ b/app/views/projects/runners/_runner.html.haml
@@ -31,6 +31,6 @@
= runner.description
- if runner.tag_list.present?
%p
- - runner.tag_list.each do |tag|
+ - runner.tag_list.sort.each do |tag|
%span.label.label-primary
= tag
diff --git a/app/views/projects/runners/show.html.haml b/app/views/projects/runners/show.html.haml
index 61b99f35d74..49415ba557b 100644
--- a/app/views/projects/runners/show.html.haml
+++ b/app/views/projects/runners/show.html.haml
@@ -28,7 +28,7 @@
%tr
%td Tags
%td
- - @runner.tag_list.each do |tag|
+ - @runner.tag_list.sort.each do |tag|
%span.label.label-primary
= tag
%tr
diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml
index dde2e2b644d..34ee4ff1937 100644
--- a/app/views/projects/snippets/_actions.html.haml
+++ b/app/views/projects/snippets/_actions.html.haml
@@ -10,7 +10,7 @@
- if can?(current_user, :create_project_snippet, @project)
= link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-inverted btn-create', title: "New snippet" do
New snippet
- - if @snippet.submittable_as_spam? && current_user.admin?
+ - if @snippet.submittable_as_spam_by?(current_user)
= link_to 'Submit as spam', mark_as_spam_namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam'
- if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet)
.visible-xs-block.dropdown
@@ -31,6 +31,6 @@
%li
= link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do
Edit
- - if @snippet.submittable_as_spam? && current_user.admin?
+ - if @snippet.submittable_as_spam_by?(current_user)
%li
= link_to 'Submit as spam', mark_as_spam_namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :post
diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml
index 485b23815bc..6b3d7d4008b 100644
--- a/app/views/projects/snippets/show.html.haml
+++ b/app/views/projects/snippets/show.html.haml
@@ -4,7 +4,7 @@
.project-snippets
%article.file-holder.snippet-file-content
- .file-title
+ .js-file-title.file-title
= blob_icon 0, @snippet.file_name
= @snippet.file_name
.file-actions
diff --git a/app/views/projects/snippets/verify.html.haml b/app/views/projects/snippets/verify.html.haml
new file mode 100644
index 00000000000..eb56f03b3f4
--- /dev/null
+++ b/app/views/projects/snippets/verify.html.haml
@@ -0,0 +1,4 @@
+- form = [@project.namespace.becomes(Namespace), @project, @snippet.becomes(Snippet)]
+
+= render 'layouts/recaptcha_verification', spammable: @snippet, form: form
+
diff --git a/app/views/projects/tree/_readme.html.haml b/app/views/projects/tree/_readme.html.haml
index a1f4e3e8ed6..bdcc160a067 100644
--- a/app/views/projects/tree/_readme.html.haml
+++ b/app/views/projects/tree/_readme.html.haml
@@ -1,5 +1,5 @@
%article.file-holder.readme-holder
- .file-title
+ .js-file-title.file-title
= blob_icon readme.mode, readme.name
= link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@ref, @path, readme.name)) do
%strong
diff --git a/app/views/projects/triggers/_index.html.haml b/app/views/projects/triggers/_index.html.haml
index 5cb1818ae54..33883facf9b 100644
--- a/app/views/projects/triggers/_index.html.haml
+++ b/app/views/projects/triggers/_index.html.haml
@@ -65,7 +65,7 @@
In the
%code .gitlab-ci.yml
of another project, include the following snippet.
- The project will be rebuilt at the end of the job.
+ The project will be rebuilt at the end of the pipeline.
%pre
:plain
@@ -89,7 +89,7 @@
%p.light
Add
%code variables[VARIABLE]=VALUE
- to an API request. Variable values can be used to distinguish between triggered jobs and normal jobs.
+ to an API request. Variable values can be used to distinguish between triggered pipelines and normal pipelines.
With cURL:
diff --git a/app/views/projects/variables/_form.html.haml b/app/views/projects/variables/_form.html.haml
index a5bae83e0ce..1ae86d258af 100644
--- a/app/views/projects/variables/_form.html.haml
+++ b/app/views/projects/variables/_form.html.haml
@@ -6,5 +6,5 @@
= f.text_field :key, class: "form-control", placeholder: "PROJECT_VARIABLE", required: true
.form-group
= f.label :value, "Value", class: "label-light"
- = f.text_area :value, class: "form-control", placeholder: "PROJECT_VARIABLE", required: true
+ = f.text_area :value, class: "form-control", placeholder: "PROJECT_VARIABLE"
= f.submit btn_text, class: "btn btn-save"
diff --git a/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml
index c74f53b4c39..3d33679f07d 100644
--- a/app/views/projects/wikis/_new.html.haml
+++ b/app/views/projects/wikis/_new.html.haml
@@ -13,5 +13,9 @@
= label_tag :new_wiki_path do
%span Page slug
= text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => namespace_project_wikis_path(@project.namespace, @project), autofocus: true
+ %span.new-wiki-page-slug-tip
+ = icon('lightbulb-o')
+ Tip: You can specify the full path for the new file.
+ We will automatically create any missing directories.
.form-actions
= button_tag 'Create Page', class: 'build-new-wiki btn btn-create'
diff --git a/app/views/projects/wikis/_pages_wiki_page.html.haml b/app/views/projects/wikis/_pages_wiki_page.html.haml
new file mode 100644
index 00000000000..6298cf6c8da
--- /dev/null
+++ b/app/views/projects/wikis/_pages_wiki_page.html.haml
@@ -0,0 +1,5 @@
+%li
+ = link_to wiki_page.title, namespace_project_wiki_path(@project.namespace, @project, wiki_page)
+ %small (#{wiki_page.format})
+ .pull-right
+ %small Last edited #{time_ago_with_tooltip(wiki_page.commit.authored_date)}
diff --git a/app/views/projects/wikis/_sidebar.html.haml b/app/views/projects/wikis/_sidebar.html.haml
index cad9c15a49e..8c582f747b3 100644
--- a/app/views/projects/wikis/_sidebar.html.haml
+++ b/app/views/projects/wikis/_sidebar.html.haml
@@ -1,4 +1,4 @@
-%aside.right-sidebar.right-sidebar-expanded.wiki-sidebar.js-wiki-sidebar
+%aside.right-sidebar.right-sidebar-expanded.wiki-sidebar.js-wiki-sidebar.js-right-sidebar{ data: { "offset-top" => "101", "spy" => "affix" } }
.block.wiki-sidebar-header.append-bottom-default
%a.gutter-toggle.pull-right.visible-xs-block.visible-sm-block.js-sidebar-wiki-toggle{ href: "#" }
= icon('angle-double-right')
@@ -12,10 +12,8 @@
.blocks-container
.block.block-first
%ul.wiki-pages
- - @sidebar_wiki_pages.each do |wiki_page|
- %li{ class: params[:id] == wiki_page.slug ? 'active' : '' }
- = link_to namespace_project_wiki_path(@project.namespace, @project, wiki_page) do
- = wiki_page.title.capitalize
+ = render @sidebar_wiki_entries, context: 'sidebar'
+
.block
= link_to namespace_project_wikis_pages_path(@project.namespace, @project), class: 'btn btn-block' do
More Pages
diff --git a/app/views/projects/wikis/_sidebar_wiki_page.html.haml b/app/views/projects/wikis/_sidebar_wiki_page.html.haml
new file mode 100644
index 00000000000..0a61d90177b
--- /dev/null
+++ b/app/views/projects/wikis/_sidebar_wiki_page.html.haml
@@ -0,0 +1,3 @@
+%li{ class: active_when(params[:id] == wiki_page.slug) }
+ = link_to namespace_project_wiki_path(@project.namespace, @project, wiki_page) do
+ = wiki_page.title.capitalize
diff --git a/app/views/projects/wikis/_wiki_directory.html.haml b/app/views/projects/wikis/_wiki_directory.html.haml
new file mode 100644
index 00000000000..0e5f32ed859
--- /dev/null
+++ b/app/views/projects/wikis/_wiki_directory.html.haml
@@ -0,0 +1,4 @@
+%li
+ = wiki_directory.slug
+ %ul
+ = render wiki_directory.pages, context: context
diff --git a/app/views/projects/wikis/_wiki_page.html.haml b/app/views/projects/wikis/_wiki_page.html.haml
new file mode 100644
index 00000000000..c84d06dad02
--- /dev/null
+++ b/app/views/projects/wikis/_wiki_page.html.haml
@@ -0,0 +1 @@
+= render "#{context}_wiki_page", wiki_page: wiki_page
diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml
index e1eaffc6884..5fba2b1a5ae 100644
--- a/app/views/projects/wikis/pages.html.haml
+++ b/app/views/projects/wikis/pages.html.haml
@@ -13,11 +13,7 @@
= icon('cloud-download')
Clone repository
- %ul.content-list
- - @wiki_pages.each do |wiki_page|
- %li
- = link_to wiki_page.title, namespace_project_wiki_path(@project.namespace, @project, wiki_page)
- %small (#{wiki_page.format})
- .pull-right
- %small Last edited #{time_ago_with_tooltip(wiki_page.commit.authored_date)}
+ %ul.wiki-pages-list.content-list
+ = render @wiki_entries, context: 'pages'
+
= paginate @wiki_pages, theme: 'gitlab'
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index 1b6dceee241..3609461b721 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -6,9 +6,11 @@
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
= icon('angle-double-left')
+ .wiki-breadcrumb
+ %span= breadcrumb(@page.slug)
+
.nav-text
%h2.wiki-page-title= @page.title.capitalize
-
%span.wiki-last-edit-by
Last edited by
%strong
diff --git a/app/views/search/_category.html.haml b/app/views/search/_category.html.haml
index 8cbecb725b5..5afb95ac430 100644
--- a/app/views/search/_category.html.haml
+++ b/app/views/search/_category.html.haml
@@ -1,70 +1,70 @@
%ul.nav-links.search-filter
- if @project
- %li{ class: ("active" if @scope == 'blobs') }
+ %li{ class: active_when(@scope == 'blobs') }
= link_to search_filter_path(scope: 'blobs') do
Code
%span.badge
= @search_results.blobs_count
- %li{ class: ("active" if @scope == 'issues') }
+ %li{ class: active_when(@scope == 'issues') }
= link_to search_filter_path(scope: 'issues') do
Issues
%span.badge
= @search_results.issues_count
- %li{ class: ("active" if @scope == 'merge_requests') }
+ %li{ class: active_when(@scope == 'merge_requests') }
= link_to search_filter_path(scope: 'merge_requests') do
Merge requests
%span.badge
= @search_results.merge_requests_count
- %li{ class: ("active" if @scope == 'milestones') }
+ %li{ class: active_when(@scope == 'milestones') }
= link_to search_filter_path(scope: 'milestones') do
Milestones
%span.badge
= @search_results.milestones_count
- %li{ class: ("active" if @scope == 'notes') }
+ %li{ class: active_when(@scope == 'notes') }
= link_to search_filter_path(scope: 'notes') do
Comments
%span.badge
= @search_results.notes_count
- %li{ class: ("active" if @scope == 'wiki_blobs') }
+ %li{ class: active_when(@scope == 'wiki_blobs') }
= link_to search_filter_path(scope: 'wiki_blobs') do
Wiki
%span.badge
= @search_results.wiki_blobs_count
- %li{ class: ("active" if @scope == 'commits') }
+ %li{ class: active_when(@scope == 'commits') }
= link_to search_filter_path(scope: 'commits') do
Commits
%span.badge
= @search_results.commits_count
- elsif @show_snippets
- %li{ class: ("active" if @scope == 'snippet_blobs') }
+ %li{ class: active_when(@scope == 'snippet_blobs') }
= link_to search_filter_path(scope: 'snippet_blobs', snippets: true, group_id: nil, project_id: nil) do
Snippet Contents
%span.badge
= @search_results.snippet_blobs_count
- %li{ class: ("active" if @scope == 'snippet_titles') }
+ %li{ class: active_when(@scope == 'snippet_titles') }
= link_to search_filter_path(scope: 'snippet_titles', snippets: true, group_id: nil, project_id: nil) do
Titles and Filenames
%span.badge
= @search_results.snippet_titles_count
- else
- %li{ class: ("active" if @scope == 'projects') }
+ %li{ class: active_when(@scope == 'projects') }
= link_to search_filter_path(scope: 'projects') do
Projects
%span.badge
= @search_results.projects_count
- %li{ class: ("active" if @scope == 'issues') }
+ %li{ class: active_when(@scope == 'issues') }
= link_to search_filter_path(scope: 'issues') do
Issues
%span.badge
= @search_results.issues_count
- %li{ class: ("active" if @scope == 'merge_requests') }
+ %li{ class: active_when(@scope == 'merge_requests') }
= link_to search_filter_path(scope: 'merge_requests') do
Merge requests
%span.badge
= @search_results.merge_requests_count
- %li{ class: ("active" if @scope == 'milestones') }
+ %li{ class: active_when(@scope == 'milestones') }
= link_to search_filter_path(scope: 'milestones') do
Milestones
%span.badge
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
index 7fe2bce3e7c..22004ecacbc 100644
--- a/app/views/search/_results.html.haml
+++ b/app/views/search/_results.html.haml
@@ -11,7 +11,7 @@
.results.prepend-top-10
- if @scope == 'commits'
- %ul.list-unstyled
+ %ul.content-list.commit-list.table-list.table-wide
= render partial: "search/results/commit", collection: @search_objects
- else
.search-results
diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml
index 9e8adc82583..7f1f807e2e7 100644
--- a/app/views/search/results/_blob.html.haml
+++ b/app/views/search/results/_blob.html.haml
@@ -1,7 +1,7 @@
- file_name, blob = blob
.blob-result
.file-holder
- .file-title
+ .js-file-title.file-title
- ref = @search_results.repository_ref
- blob_link = namespace_project_blob_path(@project.namespace, @project, tree_join(ref, file_name))
= link_to blob_link do
diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml
index 23ca6479414..f84be600df8 100644
--- a/app/views/search/results/_snippet_blob.html.haml
+++ b/app/views/search/results/_snippet_blob.html.haml
@@ -7,46 +7,39 @@
= snippet.title
by
= link_to user_snippets_path(snippet.author) do
- = image_tag avatar_icon(snippet.author_email), class: "avatar avatar-inline s16", alt: ''
+ = image_tag avatar_icon(snippet.author), class: "avatar avatar-inline s16", alt: ''
= snippet.author_name
%span.light= time_ago_with_tooltip(snippet.created_at)
%h4.snippet-title
- snippet_path = reliable_snippet_path(snippet)
- = link_to snippet_path do
- .file-holder
- .file-title
+ .file-holder
+ .js-file-title.file-title
+ = link_to snippet_path do
%i.fa.fa-file
%strong= snippet.file_name
- - if markup?(snippet.file_name)
- .file-content.wiki
+ - if markup?(snippet.file_name)
+ .file-content.wiki
+ - snippet_chunks.each do |chunk|
+ - unless chunk[:data].empty?
+ = render_markup(snippet.file_name, chunk[:data])
+ - else
+ .file-content.code
+ .nothing-here-block Empty file
+ - else
+ .file-content.code.js-syntax-highlight
+ .line-numbers
- snippet_chunks.each do |chunk|
- unless chunk[:data].empty?
- = render_markup(snippet.file_name, chunk[:data])
+ - Gitlab::Git::Util.count_lines(chunk[:data]).times do |index|
+ - offset = defined?(chunk[:start_line]) ? chunk[:start_line] : 1
+ - i = index + offset
+ = link_to snippet_path+"#L#{i}", id: "L#{i}", rel: "#L#{i}", class: "diff-line-num" do
+ %i.fa.fa-link
+ = i
+ .blob-content
+ - snippet_chunks.each do |chunk|
+ - unless chunk[:data].empty?
+ = highlight(snippet.file_name, chunk[:data], repository: nil, plain: snippet.no_highlighting?)
- else
.file-content.code
.nothing-here-block Empty file
- - else
- .file-content.code.js-syntax-highlight
- .line-numbers
- - snippet_chunks.each do |chunk|
- - unless chunk[:data].empty?
- - Gitlab::Git::Util.count_lines(chunk[:data]).times do |index|
- - offset = defined?(chunk[:start_line]) ? chunk[:start_line] : 1
- - i = index + offset
- = link_to snippet_path+"#L#{i}", id: "L#{i}", rel: "#L#{i}", class: "diff-line-num" do
- %i.fa.fa-link
- = i
- - unless snippet == snippet_chunks.last
- %a.diff-line-num
- = "."
- %pre.code
- %code
- - snippet_chunks.each do |chunk|
- - unless chunk[:data].empty?
- = chunk[:data]
- - unless chunk == snippet_chunks.last
- %a
- = "..."
- - else
- .file-content.code
- .nothing-here-block Empty file
diff --git a/app/views/search/results/_snippet_title.html.haml b/app/views/search/results/_snippet_title.html.haml
index 704d1d01a81..026f404ce07 100644
--- a/app/views/search/results/_snippet_title.html.haml
+++ b/app/views/search/results/_snippet_title.html.haml
@@ -18,6 +18,6 @@
%span
by
= link_to user_snippets_path(snippet_title.author) do
- = image_tag avatar_icon(snippet_title.author_email), class: "avatar avatar-inline s16", alt: ''
+ = image_tag avatar_icon(snippet_title.author), class: "avatar avatar-inline s16", alt: ''
= snippet_title.author_name
%span.light= time_ago_with_tooltip(snippet_title.created_at)
diff --git a/app/views/search/results/_wiki_blob.html.haml b/app/views/search/results/_wiki_blob.html.haml
index 648d0bd76cb..d87f9df2677 100644
--- a/app/views/search/results/_wiki_blob.html.haml
+++ b/app/views/search/results/_wiki_blob.html.haml
@@ -1,7 +1,7 @@
- wiki_blob = parse_search_result(wiki_blob)
.blob-result
.file-holder
- .file-title
+ .js-file-title.file-title
= link_to namespace_project_wiki_path(@project.namespace, @project, wiki_blob.basename) do
%i.fa.fa-file
%strong
diff --git a/app/views/shared/_commit_message_container.html.haml b/app/views/shared/_commit_message_container.html.haml
index c196bc06b17..4b98ff88241 100644
--- a/app/views/shared/_commit_message_container.html.haml
+++ b/app/views/shared/_commit_message_container.html.haml
@@ -17,9 +17,9 @@
Try to keep the first line under 52 characters
and the others under 72.
- if descriptions.present?
- %p.hint.js-with-description-hint
+ .hint.js-with-description-hint
= link_to "#", class: "js-with-description-link" do
Include description in commit message
- %p.hint.js-without-description-hint.hide
+ .hint.js-without-description-hint.hide
= link_to "#", class: "js-without-description-link" do
Don't include description in commit message
diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml
index 0bc851b4256..02b7b2447ed 100644
--- a/app/views/shared/_group_form.html.haml
+++ b/app/views/shared/_group_form.html.haml
@@ -1,3 +1,4 @@
+- parent = Group.find_by(id: params[:parent_id] || @group.parent_id)
- if @group.persisted?
.form-group
= f.label :name, class: 'control-label' do
@@ -11,11 +12,15 @@
.col-sm-10
.input-group.gl-field-error-anchor
.input-group-addon
- = root_url
+ %span>= root_url
+ - if parent
+ %strong= parent.full_path + '/'
= f.text_field :path, placeholder: 'open-source', class: 'form-control',
autofocus: local_assigns[:autofocus] || false, required: true,
- pattern: Gitlab::Regex::NAMESPACE_REGEX_STR_SIMPLE,
+ pattern: Gitlab::Regex::NAMESPACE_REGEX_STR_JS,
title: 'Please choose a group name with no special characters.'
+ - if parent
+ = f.hidden_field :parent_id, value: parent.id
- if @group.persisted?
.alert.alert-warning.prepend-top-10
diff --git a/app/views/shared/_issuable_meta_data.html.haml b/app/views/shared/_issuable_meta_data.html.haml
new file mode 100644
index 00000000000..66310da5cd6
--- /dev/null
+++ b/app/views/shared/_issuable_meta_data.html.haml
@@ -0,0 +1,25 @@
+- note_count = @issuable_meta_data[issuable.id].notes_count
+- issue_votes = @issuable_meta_data[issuable.id]
+- upvotes, downvotes = issue_votes.upvotes, issue_votes.downvotes
+- issuable_url = @collection_type == "Issue" ? issue_path(issuable, anchor: 'notes') : merge_request_path(issuable, anchor: 'notes')
+- issuable_mr = @issuable_meta_data[issuable.id].merge_requests_count
+
+- if issuable_mr > 0
+ %li
+ = image_tag('icon-merge-request-unmerged', class: 'icon-merge-request-unmerged')
+ = issuable_mr
+
+- if upvotes > 0
+ %li
+ = icon('thumbs-up')
+ = upvotes
+
+- if downvotes > 0
+ %li
+ = icon('thumbs-down')
+ = downvotes
+
+%li
+ = link_to issuable_url, class: ('no-comments' if note_count.zero?) do
+ = icon('comments')
+ = note_count
diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml
index ead9b84b991..1744a597c51 100644
--- a/app/views/shared/_label.html.haml
+++ b/app/views/shared/_label.html.haml
@@ -1,6 +1,4 @@
- label_css_id = dom_id(label)
-- open_issues_count = label.open_issues_count(current_user)
-- open_merge_requests_count = label.open_merge_requests_count(current_user)
- status = label_subscription_status(label, @project).inquiry if current_user
- subject = local_assigns[:subject]
@@ -15,10 +13,10 @@
%ul
%li
= link_to_label(label, subject: subject, type: :merge_request) do
- = pluralize open_merge_requests_count, 'merge request'
+ view merge requests
%li
= link_to_label(label, subject: subject) do
- = pluralize open_issues_count, 'open issue'
+ view open issues
- if current_user && defined?(@project)
%li.label-subscription
- if label.is_a?(ProjectLabel)
@@ -40,9 +38,9 @@
.pull-right.hidden-xs.hidden-sm.hidden-md
= link_to_label(label, subject: subject, type: :merge_request, css_class: 'btn btn-transparent btn-action') do
- = pluralize open_merge_requests_count, 'merge request'
+ view merge requests
= link_to_label(label, subject: subject, css_class: 'btn btn-transparent btn-action') do
- = pluralize open_issues_count, 'open issue'
+ view open issues
- if current_user && defined?(@project)
.label-subscription.inline
diff --git a/app/views/shared/_visibility_level.html.haml b/app/views/shared/_visibility_level.html.haml
index b11257ee0e6..73efec88bb1 100644
--- a/app/views/shared/_visibility_level.html.haml
+++ b/app/views/shared/_visibility_level.html.haml
@@ -1,8 +1,11 @@
+- with_label = local_assigns.fetch(:with_label, true)
+
.form-group.project-visibility-level-holder
- = f.label :visibility_level, class: 'control-label' do
- Visibility Level
- = link_to icon('question-circle'), help_page_path("public_access/public_access")
- .col-sm-10
+ - if with_label
+ = f.label :visibility_level, class: 'control-label' do
+ Visibility Level
+ = link_to icon('question-circle'), help_page_path("public_access/public_access")
+ %div{ :class => ("col-sm-10" if with_label) }
- if can_change_visibility_level
= render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: visibility_level, form_model: form_model)
- else
diff --git a/app/views/shared/builds/_tabs.html.haml b/app/views/shared/builds/_tabs.html.haml
index b6047ece592..3baa956b910 100644
--- a/app/views/shared/builds/_tabs.html.haml
+++ b/app/views/shared/builds/_tabs.html.haml
@@ -1,23 +1,23 @@
%ul.nav-links
- %li{ class: ('active' if scope.nil?) }>
+ %li{ class: active_when(scope.nil?) }>
= link_to build_path_proc.call(nil) do
All
%span.badge.js-totalbuilds-count
= number_with_delimiter(all_builds.count(:id))
- %li{ class: ('active' if scope == 'pending') }>
+ %li{ class: active_when(scope == 'pending') }>
= link_to build_path_proc.call('pending') do
Pending
%span.badge
= number_with_delimiter(all_builds.pending.count(:id))
- %li{ class: ('active' if scope == 'running') }>
+ %li{ class: active_when(scope == 'running') }>
= link_to build_path_proc.call('running') do
Running
%span.badge
= number_with_delimiter(all_builds.running.count(:id))
- %li{ class: ('active' if scope == 'finished') }>
+ %li{ class: active_when(scope == 'finished') }>
= link_to build_path_proc.call('finished') do
Finished
%span.badge
diff --git a/app/views/shared/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml
index dd9e433491b..60ca23ef680 100644
--- a/app/views/shared/groups/_group.html.haml
+++ b/app/views/shared/groups/_group.html.haml
@@ -1,4 +1,5 @@
- group_member = local_assigns[:group_member]
+- full_name = true unless local_assigns[:full_name] == false
- css_class = '' unless local_assigns[:css_class]
- css_class += " no-description" if group.description.blank?
@@ -28,7 +29,10 @@
= image_tag group_icon(group), class: "avatar s40 hidden-xs"
.title
= link_to group, class: 'group-name' do
- = group.full_name
+ - if full_name
+ = group.full_name
+ - else
+ = group.name
- if group_member
as
diff --git a/app/views/shared/issuable/_nav.html.haml b/app/views/shared/issuable/_nav.html.haml
index 1154316c03f..ad995cbe962 100644
--- a/app/views/shared/issuable/_nav.html.haml
+++ b/app/views/shared/issuable/_nav.html.haml
@@ -3,23 +3,23 @@
- issuables = @issues || @merge_requests
%ul.nav-links.issues-state-filters
- %li{ class: ("active" if params[:state] == 'opened') }>
+ %li{ class: active_when(params[:state] == 'opened') }>
= link_to page_filter_path(state: 'opened', label: true), id: 'state-opened', title: "Filter by #{page_context_word} that are currently opened." do
#{issuables_state_counter_text(type, :opened)}
- if type == :merge_requests
- %li{ class: ("active" if params[:state] == 'merged') }>
+ %li{ class: active_when(params[:state] == 'merged') }>
= link_to page_filter_path(state: 'merged', label: true), id: 'state-merged', title: 'Filter by merge requests that are currently merged.' do
#{issuables_state_counter_text(type, :merged)}
- %li{ class: ("active" if params[:state] == 'closed') }>
+ %li{ class: active_when(params[:state] == 'closed') }>
= link_to page_filter_path(state: 'closed', label: true), id: 'state-closed', title: 'Filter by merge requests that are currently closed and unmerged.' do
#{issuables_state_counter_text(type, :closed)}
- else
- %li{ class: ("active" if params[:state] == 'closed') }>
+ %li{ class: active_when(params[:state] == 'closed') }>
= link_to page_filter_path(state: 'closed', label: true), id: 'state-all', title: 'Filter by issues that are currently closed.' do
#{issuables_state_counter_text(type, :closed)}
- %li{ class: ("active" if params[:state] == 'all') }>
+ %li{ class: active_when(params[:state] == 'all') }>
= link_to page_filter_path(state: 'all', label: true), id: 'state-all', title: "Show all #{page_context_word}." do
#{issuables_state_counter_text(type, :all)}
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index 173fa922f56..8e04b50bb8a 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -11,7 +11,7 @@
class: "check_all_issues left"
.issues-other-filters.filtered-search-container
.filtered-search-input-container
- %input.form-control.filtered-search{ placeholder: 'Search or filter results...', 'data-id' => 'filtered-search', 'data-project-id' => @project.id, 'data-username-params' => @users.to_json(only: [:id, :username]) }
+ %input.form-control.filtered-search{ placeholder: 'Search or filter results...', 'data-id' => 'filtered-search', 'data-project-id' => @project.id, 'data-username-params' => @users.to_json(only: [:id, :username]), 'data-base-endpoint' => namespace_project_path(@project.namespace, @project) }
= icon('filter')
%button.clear-search.hidden{ type: 'button' }
= icon('times')
@@ -32,7 +32,7 @@
{{hint}}
%span.js-filter-tag.dropdown-light-content
{{tag}}
- #js-dropdown-author.dropdown-menu
+ #js-dropdown-author.dropdown-menu{ data: { icon: 'pencil', hint: 'author', tag: '@author' } }
%ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true }
%li.filter-dropdown-item
%button.btn.btn-link.dropdown-user
@@ -42,7 +42,7 @@
{{name}}
%span.dropdown-light-content
@{{username}}
- #js-dropdown-assignee.dropdown-menu
+ #js-dropdown-assignee.dropdown-menu{ data: { icon: 'user', hint: 'assignee', tag: '@assignee' } }
%ul{ 'data-dropdown' => true }
%li.filter-dropdown-item{ 'data-value' => 'none' }
%button.btn.btn-link
@@ -57,7 +57,7 @@
{{name}}
%span.dropdown-light-content
@{{username}}
- #js-dropdown-milestone.dropdown-menu{ 'data-dropdown' => true }
+ #js-dropdown-milestone.dropdown-menu{ data: { icon: 'clock-o', hint: 'milestone', tag: '%milestone' } }
%ul{ 'data-dropdown' => true }
%li.filter-dropdown-item{ 'data-value' => 'none' }
%button.btn.btn-link
@@ -70,7 +70,7 @@
%li.filter-dropdown-item
%button.btn.btn-link.js-data-value
{{title}}
- #js-dropdown-label.dropdown-menu{ 'data-dropdown' => true }
+ #js-dropdown-label.dropdown-menu{ data: { icon: 'tag', hint: 'label', tag: '~label' } }
%ul{ 'data-dropdown' => true }
%li.filter-dropdown-item{ 'data-value' => 'none' }
%button.btn.btn-link
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 77fc44fa5cc..6c730e16f67 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -2,7 +2,7 @@
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('issuable')
-%aside.right-sidebar{ class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' }
+%aside.right-sidebar.js-right-sidebar{ data: { "offset-top" => "101", "spy" => "affix" }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' }
.issuable-sidebar
- can_edit_issuable = can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.block.issuable-sidebar-header
@@ -173,7 +173,7 @@
:javascript
gl.IssuableResource = new gl.SubbableResource('#{issuable_json_path(issuable)}');
new gl.IssuableTimeTracking("#{escape_javascript(serialize_issuable(issuable))}");
- new MilestoneSelect('{"namespace":"#{@project.namespace.path}","path":"#{@project.path}"}');
+ new MilestoneSelect('{"full_path":"#{@project.full_path}"}');
new LabelsSelect();
new IssuableContext('#{escape_javascript(current_user.to_json(only: [:username, :id, :name]))}');
gl.Subscription.bindAll('.subscription');
diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml
index 81b5bc1de30..1d5a61cffce 100644
--- a/app/views/shared/members/_group.html.haml
+++ b/app/views/shared/members/_group.html.haml
@@ -6,7 +6,7 @@
%span.list-item-name
= image_tag group_icon(group), class: "avatar s40", alt: ''
%strong
- = link_to group.name, group_path(group)
+ = link_to group.full_name, group_path(group)
.cgray
Joined #{time_ago_with_tooltip(group.created_at)}
- if group_link.expires?
diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml
index 659d4c905fc..8e721c9c8dd 100644
--- a/app/views/shared/members/_member.html.haml
+++ b/app/views/shared/members/_member.html.haml
@@ -19,9 +19,9 @@
%label.label.label-danger
%strong Blocked
- - if source.instance_of?(Group) && !@group
+ - if source.instance_of?(Group) && source != @group
&middot;
- = link_to source.name, source, class: "member-group-link"
+ = link_to source.full_name, source, class: "member-group-link"
.hidden-xs.cgray
- if member.request?
@@ -44,8 +44,9 @@
= link_to member.created_by.name, user_path(member.created_by)
= time_ago_with_tooltip(member.created_at)
- if show_roles
+ - current_resource = @project || @group
.controls.member-controls
- - if show_controls && (member.respond_to?(:group) && @group) || (member.respond_to?(:project) && @project)
+ - if show_controls && member.source == current_resource
- if user != current_user
= form_for member, remote: true, html: { class: 'form-horizontal js-edit-member-form' } do |f|
= f.hidden_field :access_level
@@ -60,7 +61,7 @@
= dropdown_title("Change permissions")
.dropdown-content
%ul
- - Gitlab::Access.options.each do |role, role_id|
+ - member.class.access_level_roles.each do |role, role_id|
%li
= link_to role, "javascript:void(0)",
class: ("is-active" if member.access_level == role_id),
diff --git a/app/views/shared/milestones/_form_dates.html.haml b/app/views/shared/milestones/_form_dates.html.haml
index 748b10a1298..ed94773ef89 100644
--- a/app/views/shared/milestones/_form_dates.html.haml
+++ b/app/views/shared/milestones/_form_dates.html.haml
@@ -10,6 +10,3 @@
.col-sm-10
= f.text_field :due_date, class: "datepicker form-control", placeholder: "Select due date"
%a.inline.prepend-top-5.js-clear-due-date{ href: "#" } Clear due date
-
-:javascript
- new gl.DueDateSelectors();
diff --git a/app/views/shared/milestones/_issuable.html.haml b/app/views/shared/milestones/_issuable.html.haml
index 28935c8b598..4c7d69d40d5 100644
--- a/app/views/shared/milestones/_issuable.html.haml
+++ b/app/views/shared/milestones/_issuable.html.haml
@@ -5,7 +5,7 @@
- base_url_args = [project.namespace.becomes(Namespace), project, issuable_type]
- can_update = can?(current_user, :"update_#{issuable.to_ability_name}", issuable)
-%li{ id: dom_id(issuable, 'sortable'), class: "issuable-row #{'ui-sort-disabled' unless can_update}", 'data-iid' => issuable.iid, 'data-url' => polymorphic_path(issuable) }
+%li{ id: dom_id(issuable, 'sortable'), class: "issuable-row #{'is-disabled' unless can_update}", 'data-iid' => issuable.iid, 'data-id' => issuable.id, 'data-url' => polymorphic_path(issuable) }
%span
- if show_project_name
%strong #{project.name} &middot;
diff --git a/app/views/shared/milestones/_issuables.html.haml b/app/views/shared/milestones/_issuables.html.haml
index 31eb07ca666..a93cbd1041f 100644
--- a/app/views/shared/milestones/_issuables.html.haml
+++ b/app/views/shared/milestones/_issuables.html.haml
@@ -3,11 +3,11 @@
- panel_class = primary ? 'panel-primary' : 'panel-default'
.panel{ class: panel_class }
- .panel-heading.split
- .left
+ .panel-heading
+ .title
= title
- if show_counter
- .right
+ .counter
= number_with_delimiter(issuables.size)
- class_prefix = dom_class(issuables).pluralize
diff --git a/app/views/shared/projects/_dropdown.html.haml b/app/views/shared/projects/_dropdown.html.haml
index ac028f18e50..c19697802ce 100644
--- a/app/views/shared/projects/_dropdown.html.haml
+++ b/app/views/shared/projects/_dropdown.html.haml
@@ -1,6 +1,7 @@
- @sort ||= sort_value_recently_updated
- personal = params[:personal]
- archived = params[:archived]
+- shared = params[:shared]
- namespace_id = params[:namespace_id]
.dropdown
- toggle_text = projects_sort_options_hash[@sort]
@@ -28,3 +29,14 @@
%li
= link_to filter_projects_path(namespace_id: namespace_id, sort: @sort, personal: true), class: ("is-active" if personal.present?) do
Owned by me
+ - if @group && @group.shared_projects.present?
+ %li.divider
+ %li
+ = link_to filter_projects_path(namespace_id: namespace_id, sort: @sort, shared: nil), class: ("is-active" unless shared.present?) do
+ All projects
+ %li
+ = link_to filter_projects_path(namespace_id: namespace_id, sort: @sort, shared: 0), class: ("is-active" if shared == '0') do
+ Hide shared projects
+ %li
+ = link_to filter_projects_path(namespace_id: namespace_id, sort: @sort, shared: 1), class: ("is-active" if shared == '1') do
+ Hide group projects
diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml
index 56c0f7390a5..e7f7db73223 100644
--- a/app/views/shared/snippets/_form.html.haml
+++ b/app/views/shared/snippets/_form.html.haml
@@ -18,7 +18,7 @@
= f.label :file_name, "File", class: 'control-label'
.col-sm-10
.file-holder.snippet
- .file-title
+ .js-file-title.file-title
= f.text_field :file_name, placeholder: "Optionally name this file to add code highlighting, e.g. example.rb for Ruby.", class: 'form-control snippet-file-name'
.file-content.code
%pre#editor= @snippet.content
diff --git a/app/views/sherlock/file_samples/show.html.haml b/app/views/sherlock/file_samples/show.html.haml
index 92151176fce..1a6e2542dc1 100644
--- a/app/views/sherlock/file_samples/show.html.haml
+++ b/app/views/sherlock/file_samples/show.html.haml
@@ -26,7 +26,7 @@
= @file_sample.events
%article.file-holder
- .file-title
+ .js-file-title.file-title
%i.fa.fa-file-text-o.fa-fw
%strong
= @file_sample.file
diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml
index 855a995afa9..a7f118d3f7d 100644
--- a/app/views/snippets/_actions.html.haml
+++ b/app/views/snippets/_actions.html.haml
@@ -9,7 +9,7 @@
Delete
= link_to new_snippet_path, class: "btn btn-grouped btn-inverted btn-create", title: "New snippet" do
New snippet
- - if @snippet.submittable_as_spam? && current_user.admin?
+ - if @snippet.submittable_as_spam_by?(current_user)
= link_to 'Submit as spam', mark_as_spam_snippet_path(@snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam'
.visible-xs-block.dropdown
%button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
@@ -28,6 +28,6 @@
%li
= link_to edit_snippet_path(@snippet) do
Edit
- - if @snippet.submittable_as_spam? && current_user.admin?
+ - if @snippet.submittable_as_spam_by?(current_user)
%li
= link_to 'Submit as spam', mark_as_spam_snippet_path(@snippet), method: :post
diff --git a/app/views/snippets/_snippets_scope_menu.html.haml b/app/views/snippets/_snippets_scope_menu.html.haml
index 2dda5fed647..8b6a98a054a 100644
--- a/app/views/snippets/_snippets_scope_menu.html.haml
+++ b/app/views/snippets/_snippets_scope_menu.html.haml
@@ -2,7 +2,7 @@
- include_private = local_assigns.fetch(:include_private, false)
.nav-links.snippet-scope-menu
- %li{ class: ("active" unless params[:scope]) }
+ %li{ class: active_when(params[:scope].nil?) }
= link_to subject_snippets_path(subject) do
All
%span.badge
@@ -12,19 +12,19 @@
= subject.snippets.public_and_internal.count
- if include_private
- %li{ class: ("active" if params[:scope] == "are_private") }
+ %li{ class: active_when(params[:scope] == "are_private") }
= link_to subject_snippets_path(subject, scope: 'are_private') do
Private
%span.badge
= subject.snippets.are_private.count
- %li{ class: ("active" if params[:scope] == "are_internal") }
+ %li{ class: active_when(params[:scope] == "are_internal") }
= link_to subject_snippets_path(subject, scope: 'are_internal') do
Internal
%span.badge
= subject.snippets.are_internal.count
- %li{ class: ("active" if params[:scope] == "are_public") }
+ %li{ class: active_when(params[:scope] == "are_public") }
= link_to subject_snippets_path(subject, scope: 'are_public') do
Public
%span.badge
diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml
index 837a1a0cc8c..970afbe6b64 100644
--- a/app/views/snippets/show.html.haml
+++ b/app/views/snippets/show.html.haml
@@ -3,7 +3,7 @@
= render 'shared/snippets/header'
%article.file-holder.snippet-file-content
- .file-title
+ .js-file-title.file-title
= blob_icon 0, @snippet.file_name
= @snippet.file_name
.file-actions
diff --git a/app/views/snippets/verify.html.haml b/app/views/snippets/verify.html.haml
new file mode 100644
index 00000000000..cb623ccab57
--- /dev/null
+++ b/app/views/snippets/verify.html.haml
@@ -0,0 +1,4 @@
+- form = [@snippet.becomes(Snippet)]
+
+= render 'layouts/recaptcha_verification', spammable: @snippet, form: form
+
diff --git a/app/views/users/calendar.html.haml b/app/views/users/calendar.html.haml
index 6228245d8d0..57b8845c55d 100644
--- a/app/views/users/calendar.html.haml
+++ b/app/views/users/calendar.html.haml
@@ -1,7 +1,7 @@
.clearfix.calendar
.js-contrib-calendar
.calendar-hint
- Summary of issues, merge requests, and push events
+ Summary of issues, merge requests, push events, and comments
:javascript
new Calendar(
#{@activity_dates.to_json},
diff --git a/app/views/users/calendar_activities.html.haml b/app/views/users/calendar_activities.html.haml
index b09782749f5..4afd31f788b 100644
--- a/app/views/users/calendar_activities.html.haml
+++ b/app/views/users/calendar_activities.html.haml
@@ -10,11 +10,17 @@
%i.fa.fa-clock-o
= event.created_at.to_s(:time)
- if event.push?
- #{event.action_name} #{event.ref_type} #{event.ref_name}
+ #{event.action_name} #{event.ref_type}
+ %strong
+ - commits_path = namespace_project_commits_path(event.project.namespace, event.project, event.ref_name)
+ = link_to_if event.project.repository.branch_exists?(event.ref_name), event.ref_name, commits_path
- else
= event_action_name(event)
- - if event.target
- %strong= link_to "#{event.target.to_reference}", [event.project.namespace.becomes(Namespace), event.project, event.target]
+ %strong
+ - if event.note?
+ = link_to event.note_target.to_reference, event_note_target_path(event)
+ - elsif event.target
+ = link_to event.target.to_reference, [event.project.namespace.becomes(Namespace), event.project, event.target]
at
%strong
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 44254040e4e..dc2fea450bd 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -106,6 +106,8 @@
%i.fa.fa-spinner.fa-spin
.user-calendar-activities
+ %h4.prepend-top-20
+ Most Recent Activity
.content_list{ data: { href: user_path } }
= spinner
diff --git a/app/workers/authorized_projects_worker.rb b/app/workers/authorized_projects_worker.rb
index 6abbb5a5250..0e20df506a3 100644
--- a/app/workers/authorized_projects_worker.rb
+++ b/app/workers/authorized_projects_worker.rb
@@ -16,6 +16,6 @@ class AuthorizedProjectsWorker
def perform(user_id)
user = User.find_by(id: user_id)
- user.refresh_authorized_projects if user
+ user&.refresh_authorized_projects
end
end
diff --git a/app/workers/delete_user_worker.rb b/app/workers/delete_user_worker.rb
index 3194c389b3d..3340a7be4fe 100644
--- a/app/workers/delete_user_worker.rb
+++ b/app/workers/delete_user_worker.rb
@@ -6,6 +6,8 @@ class DeleteUserWorker
delete_user = User.find(delete_user_id)
current_user = User.find(current_user_id)
- DeleteUserService.new(current_user).execute(delete_user, options.symbolize_keys)
+ Users::DestroyService.new(current_user).execute(delete_user, options.symbolize_keys)
+ rescue Gitlab::Access::AccessDeniedError => e
+ Rails.logger.warn("User could not be destroyed: #{e}")
end
end
diff --git a/app/workers/group_destroy_worker.rb b/app/workers/group_destroy_worker.rb
index a49a5fd0855..07e82767b06 100644
--- a/app/workers/group_destroy_worker.rb
+++ b/app/workers/group_destroy_worker.rb
@@ -11,6 +11,6 @@ class GroupDestroyWorker
user = User.find(user_id)
- DestroyGroupService.new(group, user).execute
+ Groups::DestroyService.new(group, user).execute
end
end
diff --git a/app/workers/irker_worker.rb b/app/workers/irker_worker.rb
index 7e44b241743..c9658b3fe17 100644
--- a/app/workers/irker_worker.rb
+++ b/app/workers/irker_worker.rb
@@ -120,8 +120,8 @@ class IrkerWorker
end
def compare_url(data, repo_path)
- sha1 = Commit::truncate_sha(data['before'])
- sha2 = Commit::truncate_sha(data['after'])
+ sha1 = Commit.truncate_sha(data['before'])
+ sha2 = Commit.truncate_sha(data['after'])
compare_url = "#{Gitlab.config.gitlab.url}/#{repo_path}/compare"
compare_url += "/#{sha1}...#{sha2}"
colorize_url compare_url
@@ -129,7 +129,7 @@ class IrkerWorker
def send_one_commit(project, hook_attrs, repo_name, branch)
commit = commit_from_id project, hook_attrs['id']
- sha = colorize_sha Commit::truncate_sha(hook_attrs['id'])
+ sha = colorize_sha Commit.truncate_sha(hook_attrs['id'])
author = hook_attrs['author']['name']
files = colorize_nb_files(files_count commit)
title = commit.title
diff --git a/changelogs/unreleased/1051-api-create-users-without-password.yml b/changelogs/unreleased/1051-api-create-users-without-password.yml
new file mode 100644
index 00000000000..24b5a73b45c
--- /dev/null
+++ b/changelogs/unreleased/1051-api-create-users-without-password.yml
@@ -0,0 +1,4 @@
+---
+title: Optionally make users created via the API set their password
+merge_request: 8957
+author: Joost Rijneveld
diff --git a/changelogs/unreleased/12726-preserve-issues-after-deleting-users.yml b/changelogs/unreleased/12726-preserve-issues-after-deleting-users.yml
new file mode 100644
index 00000000000..4a1a199673c
--- /dev/null
+++ b/changelogs/unreleased/12726-preserve-issues-after-deleting-users.yml
@@ -0,0 +1,4 @@
+---
+title: Deleting a user doesn't delete issues they've created/are assigned to
+merge_request: 7393
+author:
diff --git a/changelogs/unreleased/1363-redo-mailroom-support.yml b/changelogs/unreleased/1363-redo-mailroom-support.yml
new file mode 100644
index 00000000000..8ed206f4fdb
--- /dev/null
+++ b/changelogs/unreleased/1363-redo-mailroom-support.yml
@@ -0,0 +1,4 @@
+---
+title: Redo internals of Incoming Mail Support
+merge_request: 9385
+author:
diff --git a/changelogs/unreleased/14492-change-fork-endpoint.yml b/changelogs/unreleased/14492-change-fork-endpoint.yml
new file mode 100644
index 00000000000..39024b51b54
--- /dev/null
+++ b/changelogs/unreleased/14492-change-fork-endpoint.yml
@@ -0,0 +1,4 @@
+---
+title: Move /projects/fork/:id to /projects/:id/fork
+merge_request: 8940
+author:
diff --git a/changelogs/unreleased/17662-rename-builds.yml b/changelogs/unreleased/17662-rename-builds.yml
deleted file mode 100644
index 12f2998d1c8..00000000000
--- a/changelogs/unreleased/17662-rename-builds.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Rename Builds to Pipelines, CI/CD Pipelines, or Jobs everywhere
-merge_request: 8787
-author:
diff --git a/changelogs/unreleased/19302-wiki-page-delete-does-not-trigger-the-webhook.yml b/changelogs/unreleased/19302-wiki-page-delete-does-not-trigger-the-webhook.yml
new file mode 100644
index 00000000000..d74057dca8a
--- /dev/null
+++ b/changelogs/unreleased/19302-wiki-page-delete-does-not-trigger-the-webhook.yml
@@ -0,0 +1,4 @@
+---
+title: Execute web hooks for WikiPage delete operation
+merge_request: 8198
+author:
diff --git a/changelogs/unreleased/20452-remove-require-from-request_profiler-initializer.yml b/changelogs/unreleased/20452-remove-require-from-request_profiler-initializer.yml
deleted file mode 100644
index 965d0648adf..00000000000
--- a/changelogs/unreleased/20452-remove-require-from-request_profiler-initializer.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Don't require lib/gitlab/request_profiler/middleware.rb in config/initializers/request_profiler.rb
-merge_request:
-author:
diff --git a/changelogs/unreleased/20495-plus-icon-button.yml b/changelogs/unreleased/20495-plus-icon-button.yml
new file mode 100644
index 00000000000..0f8650eb7b6
--- /dev/null
+++ b/changelogs/unreleased/20495-plus-icon-button.yml
@@ -0,0 +1,4 @@
+---
+title: Remove plus icon from MR button on compare view
+merge_request:
+author:
diff --git a/changelogs/unreleased/20732_member_exists_409.yml b/changelogs/unreleased/20732_member_exists_409.yml
new file mode 100644
index 00000000000..135647c7ac3
--- /dev/null
+++ b/changelogs/unreleased/20732_member_exists_409.yml
@@ -0,0 +1,4 @@
+---
+title: 'Add member: Always return 409 when a member exists'
+merge_request:
+author:
diff --git a/changelogs/unreleased/20852-getting-started-project-better-blank-state-for-labels-view.yml b/changelogs/unreleased/20852-getting-started-project-better-blank-state-for-labels-view.yml
deleted file mode 100644
index eda872049fd..00000000000
--- a/changelogs/unreleased/20852-getting-started-project-better-blank-state-for-labels-view.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Added labels empty state
-merge_request: 7443
-author:
diff --git a/changelogs/unreleased/21240_snippets_line_ending.yml b/changelogs/unreleased/21240_snippets_line_ending.yml
new file mode 100644
index 00000000000..880fdd2c9ed
--- /dev/null
+++ b/changelogs/unreleased/21240_snippets_line_ending.yml
@@ -0,0 +1,4 @@
+---
+title: Download snippets with LF line-endings by default
+merge_request: 8999
+author:
diff --git a/changelogs/unreleased/21518_recaptcha_spam_issues.yml b/changelogs/unreleased/21518_recaptcha_spam_issues.yml
deleted file mode 100644
index bd6c9d7521e..00000000000
--- a/changelogs/unreleased/21518_recaptcha_spam_issues.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Use reCaptcha when an issue is identified as a spam
-merge_request: 8846
-author:
diff --git a/changelogs/unreleased/22007-unify-projects-search.yml b/changelogs/unreleased/22007-unify-projects-search.yml
deleted file mode 100644
index f43c1925ad0..00000000000
--- a/changelogs/unreleased/22007-unify-projects-search.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Unify projects search by removing /projects/:search endpoint
-merge_request: 8877
-author:
diff --git a/changelogs/unreleased/22018-api-milestone-merge-requests.yml b/changelogs/unreleased/22018-api-milestone-merge-requests.yml
new file mode 100644
index 00000000000..ccad2ec838c
--- /dev/null
+++ b/changelogs/unreleased/22018-api-milestone-merge-requests.yml
@@ -0,0 +1,4 @@
+---
+title: Adds API endpoint to fetch all merge request for a single milestone
+merge_request:
+author: Joren De Groof
diff --git a/changelogs/unreleased/22132-rename-branch-name-params-to-branch.yml b/changelogs/unreleased/22132-rename-branch-name-params-to-branch.yml
new file mode 100644
index 00000000000..028923b83cf
--- /dev/null
+++ b/changelogs/unreleased/22132-rename-branch-name-params-to-branch.yml
@@ -0,0 +1,4 @@
+---
+title: Standardize branch name params as branch on V4 API
+merge_request: 8936
+author:
diff --git a/changelogs/unreleased/22466-task-list-alignment.yml b/changelogs/unreleased/22466-task-list-alignment.yml
new file mode 100644
index 00000000000..6e6ccb873ec
--- /dev/null
+++ b/changelogs/unreleased/22466-task-list-alignment.yml
@@ -0,0 +1,4 @@
+---
+title: Align task list checkboxes
+merge_request: 6487
+author: Jared Deckard <jared.deckard@gmail.com>
diff --git a/changelogs/unreleased/22638-creating-a-branch-matching-a-wildcard-fails.yml b/changelogs/unreleased/22638-creating-a-branch-matching-a-wildcard-fails.yml
deleted file mode 100644
index 2c6883bcf7b..00000000000
--- a/changelogs/unreleased/22638-creating-a-branch-matching-a-wildcard-fails.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Allow creating protected branches when user can merge to such branch
-merge_request: 8458
-author:
diff --git a/changelogs/unreleased/22645-add-discussion-contribs-to-calendar.yml b/changelogs/unreleased/22645-add-discussion-contribs-to-calendar.yml
new file mode 100644
index 00000000000..9b3c2bd9278
--- /dev/null
+++ b/changelogs/unreleased/22645-add-discussion-contribs-to-calendar.yml
@@ -0,0 +1,4 @@
+---
+title: Add discussion events to contributions calendar
+merge_request: 8821
+author:
diff --git a/changelogs/unreleased/22818-licence-gitignore-and-yml-endpoints-removal.yml b/changelogs/unreleased/22818-licence-gitignore-and-yml-endpoints-removal.yml
new file mode 100644
index 00000000000..05d5993ddf3
--- /dev/null
+++ b/changelogs/unreleased/22818-licence-gitignore-and-yml-endpoints-removal.yml
@@ -0,0 +1,4 @@
+---
+title: V3 deprecated templates endpoints removal
+merge_request: 8853
+author:
diff --git a/changelogs/unreleased/22974-trigger-service-events-through-api.yml b/changelogs/unreleased/22974-trigger-service-events-through-api.yml
deleted file mode 100644
index 57106e8c676..00000000000
--- a/changelogs/unreleased/22974-trigger-service-events-through-api.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Adds service trigger events to api
-merge_request: 8324
-author:
diff --git a/changelogs/unreleased/23061-consolidate-project-lists.yml b/changelogs/unreleased/23061-consolidate-project-lists.yml
new file mode 100644
index 00000000000..dbb8fed55c0
--- /dev/null
+++ b/changelogs/unreleased/23061-consolidate-project-lists.yml
@@ -0,0 +1,4 @@
+---
+title: 'API: Consolidate /projects endpoint'
+merge_request: 8962
+author:
diff --git a/changelogs/unreleased/23104-remove-public-param-for-projects.yml b/changelogs/unreleased/23104-remove-public-param-for-projects.yml
new file mode 100644
index 00000000000..78eb785279f
--- /dev/null
+++ b/changelogs/unreleased/23104-remove-public-param-for-projects.yml
@@ -0,0 +1,4 @@
+---
+title: 'API: remove `public` param for projects'
+merge_request: 8736
+author:
diff --git a/changelogs/unreleased/23524-notify-automerge-user-of-failed-build.yml b/changelogs/unreleased/23524-notify-automerge-user-of-failed-build.yml
deleted file mode 100644
index 268be6b9b83..00000000000
--- a/changelogs/unreleased/23524-notify-automerge-user-of-failed-build.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Create a TODO for user who set auto-merge when a build fails, merge conflict occurs
-merge_request: 8056
-author: twonegatives
diff --git a/changelogs/unreleased/23535-folders-in-wiki-repository.yml b/changelogs/unreleased/23535-folders-in-wiki-repository.yml
new file mode 100644
index 00000000000..05212b608d4
--- /dev/null
+++ b/changelogs/unreleased/23535-folders-in-wiki-repository.yml
@@ -0,0 +1,4 @@
+---
+title: Show directory hierarchy when listing wiki pages
+merge_request: 8133
+author: Alex Braha Stoll
diff --git a/changelogs/unreleased/23634-remove-project-grouping.yml b/changelogs/unreleased/23634-remove-project-grouping.yml
deleted file mode 100644
index dde8b2d1815..00000000000
--- a/changelogs/unreleased/23634-remove-project-grouping.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Don't group issues by project on group-level and dashboard issue indexes.
-merge_request: 8111
-author: Bernardo Castro
diff --git a/changelogs/unreleased/23767-disable-storing-of-sensitive-information.yml b/changelogs/unreleased/23767-disable-storing-of-sensitive-information.yml
deleted file mode 100644
index 587ef4f9a73..00000000000
--- a/changelogs/unreleased/23767-disable-storing-of-sensitive-information.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix disable storing of sensitive information when importing a new repo
-merge_request: 8885
-author: Bernard Pietraga
diff --git a/changelogs/unreleased/23819-fix-milestone-counters-to-top-right-of-panel-headings.yml b/changelogs/unreleased/23819-fix-milestone-counters-to-top-right-of-panel-headings.yml
new file mode 100644
index 00000000000..628db8a5419
--- /dev/null
+++ b/changelogs/unreleased/23819-fix-milestone-counters-to-top-right-of-panel-headings.yml
@@ -0,0 +1,4 @@
+---
+title: Fix position of counter in milestone panels
+merge_request: 7842
+author: Andrew Smith (EspadaV8)
diff --git a/changelogs/unreleased/24147-delete-env-button.yml b/changelogs/unreleased/24147-delete-env-button.yml
deleted file mode 100644
index 14e80cacbfb..00000000000
--- a/changelogs/unreleased/24147-delete-env-button.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Adds back ability to stop all environments
-merge_request: 7379
-author:
diff --git a/changelogs/unreleased/24333-close-issues-with-merge-request-title-ui.yml b/changelogs/unreleased/24333-close-issues-with-merge-request-title-ui.yml
new file mode 100644
index 00000000000..fa137a29cb4
--- /dev/null
+++ b/changelogs/unreleased/24333-close-issues-with-merge-request-title-ui.yml
@@ -0,0 +1,5 @@
+---
+title: Show Issues mentioned / being closed from a Merge Requests title below the
+ 'Accept Merge Request' button
+merge_request: 9194
+author: Jan Christophersen
diff --git a/changelogs/unreleased/24462-reduce_ldap_queries_for_lfs.yml b/changelogs/unreleased/24462-reduce_ldap_queries_for_lfs.yml
deleted file mode 100644
index 05fbd8f0bf2..00000000000
--- a/changelogs/unreleased/24462-reduce_ldap_queries_for_lfs.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Reduce hits to LDAP on Git HTTP auth by reordering auth mechanisms
-merge_request: 8752
-author:
diff --git a/changelogs/unreleased/24606-force-password-reset-on-next-login.yml b/changelogs/unreleased/24606-force-password-reset-on-next-login.yml
deleted file mode 100644
index fd671d04a9f..00000000000
--- a/changelogs/unreleased/24606-force-password-reset-on-next-login.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Force new password after password reset via API
-merge_request:
-author: George Andrinopoulos
diff --git a/changelogs/unreleased/24716-fix-ctrl-click-links.yml b/changelogs/unreleased/24716-fix-ctrl-click-links.yml
deleted file mode 100644
index 13de5db5e41..00000000000
--- a/changelogs/unreleased/24716-fix-ctrl-click-links.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix Ctrl+Click support for Todos and Merge Request page tabs
-merge_request: 8898
-author:
diff --git a/changelogs/unreleased/24795_refactor_merge_request_build_service.yml b/changelogs/unreleased/24795_refactor_merge_request_build_service.yml
deleted file mode 100644
index b735fb57649..00000000000
--- a/changelogs/unreleased/24795_refactor_merge_request_build_service.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Refactor MergeRequests::BuildService
-merge_request: 8462
-author: Rydkin Maxim
diff --git a/changelogs/unreleased/24833-Allow-to-search-by-commit-hash-within-project.yml b/changelogs/unreleased/24833-Allow-to-search-by-commit-hash-within-project.yml
deleted file mode 100644
index be66c370f36..00000000000
--- a/changelogs/unreleased/24833-Allow-to-search-by-commit-hash-within-project.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: 'Allows to search within project by commit hash'
-merge_request:
-author: YarNayar
diff --git a/changelogs/unreleased/24923_nested_tasks.yml b/changelogs/unreleased/24923_nested_tasks.yml
deleted file mode 100644
index de35cad3dd6..00000000000
--- a/changelogs/unreleased/24923_nested_tasks.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix nested tasks in ordered list
-merge_request: 8626
-author:
diff --git a/changelogs/unreleased/24976-start-of-line-mention.yml b/changelogs/unreleased/24976-start-of-line-mention.yml
new file mode 100644
index 00000000000..99208aac87c
--- /dev/null
+++ b/changelogs/unreleased/24976-start-of-line-mention.yml
@@ -0,0 +1,4 @@
+---
+title: Added a feature to create a 'directly addressed' Todo when mentioned in the beginning of a line.
+merge_request: 7926
+author: Ershad Kunnakkadan
diff --git a/changelogs/unreleased/25134-mobile-issue-view-doesn-t-show-organization-membership.yml b/changelogs/unreleased/25134-mobile-issue-view-doesn-t-show-organization-membership.yml
deleted file mode 100644
index d35ad0be0db..00000000000
--- a/changelogs/unreleased/25134-mobile-issue-view-doesn-t-show-organization-membership.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Show organisation membership and delete comment on smaller viewports, plus change comment author name to username
-merge_request:
-author:
diff --git a/changelogs/unreleased/25312-search-input-cmd-click-issue.yml b/changelogs/unreleased/25312-search-input-cmd-click-issue.yml
deleted file mode 100644
index 56e03a48692..00000000000
--- a/changelogs/unreleased/25312-search-input-cmd-click-issue.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Prevent removal of input fields if it is the parent dropdown element
-merge_request: 8397
-author:
diff --git a/changelogs/unreleased/25360-remove-flash-warning-from-login-page.yml b/changelogs/unreleased/25360-remove-flash-warning-from-login-page.yml
deleted file mode 100644
index 50a5c879446..00000000000
--- a/changelogs/unreleased/25360-remove-flash-warning-from-login-page.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove flash warning from login page
-merge_request: 8864
-author: Gerald J. Padilla
diff --git a/changelogs/unreleased/25460-replace-word-users-with-members.yml b/changelogs/unreleased/25460-replace-word-users-with-members.yml
deleted file mode 100644
index dac90eaa34d..00000000000
--- a/changelogs/unreleased/25460-replace-word-users-with-members.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Replace word user with member
-merge_request: 8872
-author:
diff --git a/changelogs/unreleased/25465-todo-done-clicking-is-kind-of-unsafe.yml b/changelogs/unreleased/25465-todo-done-clicking-is-kind-of-unsafe.yml
new file mode 100644
index 00000000000..e9d46f6b122
--- /dev/null
+++ b/changelogs/unreleased/25465-todo-done-clicking-is-kind-of-unsafe.yml
@@ -0,0 +1,4 @@
+---
+title: Todo done clicking is kind of unusable
+merge_request: 8691
+author: Jacopo Beschi @jacopo-beschi
diff --git a/changelogs/unreleased/25624-anticipate-obstacles-to-removing-turbolinks.yml b/changelogs/unreleased/25624-anticipate-obstacles-to-removing-turbolinks.yml
deleted file mode 100644
index d7f950d7be9..00000000000
--- a/changelogs/unreleased/25624-anticipate-obstacles-to-removing-turbolinks.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove turbolinks.
-merge_request: !8570
-author:
diff --git a/changelogs/unreleased/25709-diff-file-overflow.yml b/changelogs/unreleased/25709-diff-file-overflow.yml
new file mode 100644
index 00000000000..7d1b2b36ab8
--- /dev/null
+++ b/changelogs/unreleased/25709-diff-file-overflow.yml
@@ -0,0 +1,4 @@
+---
+title: Responsive title in diffs inline, side by side, with and without sidebar
+merge_request: 8475
+author:
diff --git a/changelogs/unreleased/25811-pipeline-number-and-url-do-not-update-when-new-pipeline-is-triggered.yml b/changelogs/unreleased/25811-pipeline-number-and-url-do-not-update-when-new-pipeline-is-triggered.yml
deleted file mode 100644
index f74e9fa8b6d..00000000000
--- a/changelogs/unreleased/25811-pipeline-number-and-url-do-not-update-when-new-pipeline-is-triggered.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Update pipeline and commit links when CI status is updated
-merge_request: 8351
-author:
diff --git a/changelogs/unreleased/25910-convert-manual-action-icons-to-svg-to-propperly-position-them.yml b/changelogs/unreleased/25910-convert-manual-action-icons-to-svg-to-propperly-position-them.yml
deleted file mode 100644
index 9506692dd40..00000000000
--- a/changelogs/unreleased/25910-convert-manual-action-icons-to-svg-to-propperly-position-them.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Convert pipeline action icons to svg to have them propperly positioned
-merge_request:
-author:
diff --git a/changelogs/unreleased/25989-fix-rogue-scrollbars-on-comments.yml b/changelogs/unreleased/25989-fix-rogue-scrollbars-on-comments.yml
deleted file mode 100644
index e67a9c0da15..00000000000
--- a/changelogs/unreleased/25989-fix-rogue-scrollbars-on-comments.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove rogue scrollbars for issue comments with inline elements
-merge_request:
-author:
diff --git a/changelogs/unreleased/26059-segoe-ui-vertical.yml b/changelogs/unreleased/26059-segoe-ui-vertical.yml
deleted file mode 100644
index fc3f1af5b61..00000000000
--- a/changelogs/unreleased/26059-segoe-ui-vertical.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Align Segoe UI label text
-merge_request:
-author:
diff --git a/changelogs/unreleased/26068_tasklist_issue.yml b/changelogs/unreleased/26068_tasklist_issue.yml
deleted file mode 100644
index c938351b8a7..00000000000
--- a/changelogs/unreleased/26068_tasklist_issue.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Don’t count tasks that are not defined as list items correctly
-merge_request: 8526
-author:
diff --git a/changelogs/unreleased/26087-asciidoc-cicd-badges-snippet.yml b/changelogs/unreleased/26087-asciidoc-cicd-badges-snippet.yml
new file mode 100644
index 00000000000..799c5277207
--- /dev/null
+++ b/changelogs/unreleased/26087-asciidoc-cicd-badges-snippet.yml
@@ -0,0 +1,4 @@
+---
+title: Added AsciiDoc Snippet to CI/CD Badges
+merge_request: 9164
+author: Jan Christophersen
diff --git a/changelogs/unreleased/26117-sort-pipeline-for-commit.yml b/changelogs/unreleased/26117-sort-pipeline-for-commit.yml
deleted file mode 100644
index b2f5294d380..00000000000
--- a/changelogs/unreleased/26117-sort-pipeline-for-commit.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add sorting pipeline for a commit
-merge_request: 8319
-author: Takuya Noguchi
diff --git a/changelogs/unreleased/26186-diff-view-plus-and-minus-signs-as-part-of-line-number.yml b/changelogs/unreleased/26186-diff-view-plus-and-minus-signs-as-part-of-line-number.yml
deleted file mode 100644
index 565672917b2..00000000000
--- a/changelogs/unreleased/26186-diff-view-plus-and-minus-signs-as-part-of-line-number.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Color + and - signs in diffs to increase code legibility
-merge_request:
-author:
diff --git a/changelogs/unreleased/26206-fix-download-dropdown.yml b/changelogs/unreleased/26206-fix-download-dropdown.yml
new file mode 100644
index 00000000000..a6c101375bb
--- /dev/null
+++ b/changelogs/unreleased/26206-fix-download-dropdown.yml
@@ -0,0 +1,4 @@
+---
+title: Set dropdown height fixed to 250px and make it scrollable
+merge_request: 9063
+author:
diff --git a/changelogs/unreleased/26286-most-recent-activity-profile-header.yml b/changelogs/unreleased/26286-most-recent-activity-profile-header.yml
new file mode 100644
index 00000000000..74d5a43a804
--- /dev/null
+++ b/changelogs/unreleased/26286-most-recent-activity-profile-header.yml
@@ -0,0 +1,4 @@
+---
+title: Added 'Most Recent Activity' header to the User Profile page
+merge_request: 9189
+author: Jan Christophersen
diff --git a/changelogs/unreleased/26287-link-branch-in-calendar-activity.yml b/changelogs/unreleased/26287-link-branch-in-calendar-activity.yml
new file mode 100644
index 00000000000..35855578d21
--- /dev/null
+++ b/changelogs/unreleased/26287-link-branch-in-calendar-activity.yml
@@ -0,0 +1,4 @@
+---
+title: Add Links to Branches in Calendar Activity
+merge_request: 9224
+author: Jan Christophersen
diff --git a/changelogs/unreleased/26315-unify-labels-filter-behavior.yml b/changelogs/unreleased/26315-unify-labels-filter-behavior.yml
new file mode 100644
index 00000000000..cd2f40c94fe
--- /dev/null
+++ b/changelogs/unreleased/26315-unify-labels-filter-behavior.yml
@@ -0,0 +1,4 @@
+---
+title: Unify issues search behavior by always filtering when ALL labels matches
+merge_request: 8849
+author:
diff --git a/changelogs/unreleased/26348-cleanup-navigation-order-groups.yml b/changelogs/unreleased/26348-cleanup-navigation-order-groups.yml
new file mode 100644
index 00000000000..ce888baa32f
--- /dev/null
+++ b/changelogs/unreleased/26348-cleanup-navigation-order-groups.yml
@@ -0,0 +1,4 @@
+---
+title: Clean-up Groups navigation order
+merge_request: 9309
+author:
diff --git a/changelogs/unreleased/26379-iid-param.yml b/changelogs/unreleased/26379-iid-param.yml
new file mode 100644
index 00000000000..ac743e68d6f
--- /dev/null
+++ b/changelogs/unreleased/26379-iid-param.yml
@@ -0,0 +1,4 @@
+---
+title: add :iids param to IssuableFinder (resolve technical dept)
+merge_request: 9222
+author: mhasbini
diff --git a/changelogs/unreleased/26445-accessible-piplelines-buttons.yml b/changelogs/unreleased/26445-accessible-piplelines-buttons.yml
deleted file mode 100644
index fb5274e5253..00000000000
--- a/changelogs/unreleased/26445-accessible-piplelines-buttons.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Improve button accessibility on pipelines page
-merge_request: 8561
-author:
diff --git a/changelogs/unreleased/26447-fix-tab-list-order.yml b/changelogs/unreleased/26447-fix-tab-list-order.yml
deleted file mode 100644
index 351c53bd076..00000000000
--- a/changelogs/unreleased/26447-fix-tab-list-order.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix tab index order on branch commits list page
-merge_request:
-author: Ryan Harris
diff --git a/changelogs/unreleased/26468-fix-users-sort-in-admin-area.yml b/changelogs/unreleased/26468-fix-users-sort-in-admin-area.yml
deleted file mode 100644
index 87ae8233c4a..00000000000
--- a/changelogs/unreleased/26468-fix-users-sort-in-admin-area.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix Sort by Recent Sign-in in Admin Area
-merge_request: 8637
-author: Poornima M
diff --git a/changelogs/unreleased/26500-informative-slack-notifications.yml b/changelogs/unreleased/26500-informative-slack-notifications.yml
new file mode 100644
index 00000000000..342235424f4
--- /dev/null
+++ b/changelogs/unreleased/26500-informative-slack-notifications.yml
@@ -0,0 +1,4 @@
+---
+title: Add user & build links in Slack Notifications
+merge_request: 8641
+author: Poornima M
diff --git a/changelogs/unreleased/26651-cannot-move-project-into-group.yml b/changelogs/unreleased/26651-cannot-move-project-into-group.yml
new file mode 100644
index 00000000000..244a19a627d
--- /dev/null
+++ b/changelogs/unreleased/26651-cannot-move-project-into-group.yml
@@ -0,0 +1,4 @@
+---
+title: Specify in the documentation that only projects owners can transfer projects
+merge_request:
+author:
diff --git a/changelogs/unreleased/26703-todos-count.yml b/changelogs/unreleased/26703-todos-count.yml
new file mode 100644
index 00000000000..24fd0c406e2
--- /dev/null
+++ b/changelogs/unreleased/26703-todos-count.yml
@@ -0,0 +1,4 @@
+---
+title: show 99+ for large count in todos notification bell
+merge_request: 9171
+author: mhasbini
diff --git a/changelogs/unreleased/26705-filter-todos-by-manual-add.yml b/changelogs/unreleased/26705-filter-todos-by-manual-add.yml
new file mode 100644
index 00000000000..3521496a20e
--- /dev/null
+++ b/changelogs/unreleased/26705-filter-todos-by-manual-add.yml
@@ -0,0 +1,4 @@
+---
+title: Filter todos by manual add
+merge_request: 8691
+author: Jacopo Beschi @jacopo-beschi
diff --git a/changelogs/unreleased/26744-add-omniauth-oauth2-generic-strategy.yml b/changelogs/unreleased/26744-add-omniauth-oauth2-generic-strategy.yml
new file mode 100644
index 00000000000..15da43b8091
--- /dev/null
+++ b/changelogs/unreleased/26744-add-omniauth-oauth2-generic-strategy.yml
@@ -0,0 +1,3 @@
+title: Add the oauth2_generic OmniAuth strategy
+merge_request: 9048
+author: Joe Marty \ No newline at end of file
diff --git a/changelogs/unreleased/26787-add-copy-icon-hover-state.yml b/changelogs/unreleased/26787-add-copy-icon-hover-state.yml
deleted file mode 100644
index 31f1812c6f8..00000000000
--- a/changelogs/unreleased/26787-add-copy-icon-hover-state.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add hover style to copy icon on commit page header
-merge_request:
-author: Ryan Harris
diff --git a/changelogs/unreleased/26852-fix-slug-for-openshift.yml b/changelogs/unreleased/26852-fix-slug-for-openshift.yml
deleted file mode 100644
index fb65b068b23..00000000000
--- a/changelogs/unreleased/26852-fix-slug-for-openshift.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Avoid repeated dashes in $CI_ENVIRONMENT_SLUG
-merge_request: 8638
-author:
diff --git a/changelogs/unreleased/26863-Remove-hover-animation-from-row-elements.yml b/changelogs/unreleased/26863-Remove-hover-animation-from-row-elements.yml
deleted file mode 100644
index 8dfabf87c2a..00000000000
--- a/changelogs/unreleased/26863-Remove-hover-animation-from-row-elements.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove hover animation from row elements
-merge_request:
-author:
diff --git a/changelogs/unreleased/26875-builds-api-endpoint-skipped-scope.yml b/changelogs/unreleased/26875-builds-api-endpoint-skipped-scope.yml
new file mode 100644
index 00000000000..3d6400cba76
--- /dev/null
+++ b/changelogs/unreleased/26875-builds-api-endpoint-skipped-scope.yml
@@ -0,0 +1,4 @@
+---
+title: Add all available statuses to scope filter for project builds endpoint
+merge_request:
+author: George Andrinopoulos
diff --git a/changelogs/unreleased/26900-pipelines-tabs.yml b/changelogs/unreleased/26900-pipelines-tabs.yml
new file mode 100644
index 00000000000..f08514c621f
--- /dev/null
+++ b/changelogs/unreleased/26900-pipelines-tabs.yml
@@ -0,0 +1,4 @@
+---
+title: Adds Pending and Finished tabs to pipelines page
+merge_request:
+author:
diff --git a/changelogs/unreleased/26920-hover-cursor-on-pagination-element.yml b/changelogs/unreleased/26920-hover-cursor-on-pagination-element.yml
deleted file mode 100644
index ea567437ac2..00000000000
--- a/changelogs/unreleased/26920-hover-cursor-on-pagination-element.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixes hover cursor on pipeline pagenation
-merge_request: 9003
-author:
diff --git a/changelogs/unreleased/26947-build-status-self-link.yml b/changelogs/unreleased/26947-build-status-self-link.yml
deleted file mode 100644
index 15c5821874e..00000000000
--- a/changelogs/unreleased/26947-build-status-self-link.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add link verification to badge partial in order to render a badge without a link
-merge_request: 8740
-author:
diff --git a/changelogs/unreleased/26957-tanuki-anim-hang.yml b/changelogs/unreleased/26957-tanuki-anim-hang.yml
new file mode 100644
index 00000000000..c7b4b9ebdfd
--- /dev/null
+++ b/changelogs/unreleased/26957-tanuki-anim-hang.yml
@@ -0,0 +1,4 @@
+---
+title: don't animate logo when downloading files
+merge_request:
+author:
diff --git a/changelogs/unreleased/26982-improve-pipeline-status-icon-linking-in-widgets.yml b/changelogs/unreleased/26982-improve-pipeline-status-icon-linking-in-widgets.yml
deleted file mode 100644
index c5c57af5aaf..00000000000
--- a/changelogs/unreleased/26982-improve-pipeline-status-icon-linking-in-widgets.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Improve pipeline status icon linking in widgets
-merge_request:
-author:
diff --git a/changelogs/unreleased/27013-regression-in-commit-title-bar.yml b/changelogs/unreleased/27013-regression-in-commit-title-bar.yml
deleted file mode 100644
index 7cb5e4b273d..00000000000
--- a/changelogs/unreleased/27013-regression-in-commit-title-bar.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix commit title bar and repository view copy clipboard button order on last commit in repository view
-merge_request:
-author:
diff --git a/changelogs/unreleased/27014-fix-pipeline-tooltip-wrapping.yml b/changelogs/unreleased/27014-fix-pipeline-tooltip-wrapping.yml
deleted file mode 100644
index f0301c849b6..00000000000
--- a/changelogs/unreleased/27014-fix-pipeline-tooltip-wrapping.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix mini-pipeline stage tooltip text wrapping
-merge_request:
-author:
diff --git a/changelogs/unreleased/27021-line-numbers-now-in-copy-pasta-data.yml b/changelogs/unreleased/27021-line-numbers-now-in-copy-pasta-data.yml
deleted file mode 100644
index b5584749098..00000000000
--- a/changelogs/unreleased/27021-line-numbers-now-in-copy-pasta-data.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Prevent copying of line numbers in parallel diff view
-merge_request: 8706
-author:
diff --git a/changelogs/unreleased/27032-add-a-house-keeping-api-call.yml b/changelogs/unreleased/27032-add-a-house-keeping-api-call.yml
new file mode 100644
index 00000000000..a9f70e339c0
--- /dev/null
+++ b/changelogs/unreleased/27032-add-a-house-keeping-api-call.yml
@@ -0,0 +1,4 @@
+---
+title: Add housekeeping endpoint for Projects API
+merge_request: 9421
+author:
diff --git a/changelogs/unreleased/27178-update-builds-link-in-project-settings.yml b/changelogs/unreleased/27178-update-builds-link-in-project-settings.yml
deleted file mode 100644
index 52406bba464..00000000000
--- a/changelogs/unreleased/27178-update-builds-link-in-project-settings.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Updated builds info link on the project settings page
-merge_request:
-author: Ryan Harris
diff --git a/changelogs/unreleased/27240-make-progress-bars-consistent.yml b/changelogs/unreleased/27240-make-progress-bars-consistent.yml
deleted file mode 100644
index 3f902fb324e..00000000000
--- a/changelogs/unreleased/27240-make-progress-bars-consistent.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: 27240 Make progress bars consistent
-merge_request:
-author:
diff --git a/changelogs/unreleased/27267-unnecessary-queries-from-projects-dashboard-atom-and-json.yml b/changelogs/unreleased/27267-unnecessary-queries-from-projects-dashboard-atom-and-json.yml
deleted file mode 100644
index 7b307b501f4..00000000000
--- a/changelogs/unreleased/27267-unnecessary-queries-from-projects-dashboard-atom-and-json.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove unnecessary queries for .atom and .json in Dashboard::ProjectsController#index
-merge_request: 8956
-author:
diff --git a/changelogs/unreleased/27277-small-mini-pipeline-graph-glitch-upon-hover.yml b/changelogs/unreleased/27277-small-mini-pipeline-graph-glitch-upon-hover.yml
deleted file mode 100644
index 9456251025b..00000000000
--- a/changelogs/unreleased/27277-small-mini-pipeline-graph-glitch-upon-hover.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: fixed small mini pipeline graph line glitch
-merge_request: 8804
-author:
diff --git a/changelogs/unreleased/27287-label-dropdown-error-messages.yml b/changelogs/unreleased/27287-label-dropdown-error-messages.yml
new file mode 100644
index 00000000000..dfd4102c324
--- /dev/null
+++ b/changelogs/unreleased/27287-label-dropdown-error-messages.yml
@@ -0,0 +1,4 @@
+---
+title: Fix displaying error messages for create label dropdown
+merge_request: 9058
+author: Tom Koole
diff --git a/changelogs/unreleased/27291-unify-mr-diff-file-buttons.yml b/changelogs/unreleased/27291-unify-mr-diff-file-buttons.yml
deleted file mode 100644
index 293aab67d39..00000000000
--- a/changelogs/unreleased/27291-unify-mr-diff-file-buttons.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Unify MR diff file button style
-merge_request: 8874
-author:
diff --git a/changelogs/unreleased/27321-double-separator-line-in-edit-projects-settings.yml b/changelogs/unreleased/27321-double-separator-line-in-edit-projects-settings.yml
deleted file mode 100644
index 502927cd160..00000000000
--- a/changelogs/unreleased/27321-double-separator-line-in-edit-projects-settings.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Only render hr when user can't archive project.
-merge_request: !8917
-author:
diff --git a/changelogs/unreleased/27332-mini-pipeline-graph-with-many-stages-has-no-line-spacing-in-firefox-and-safari.yml b/changelogs/unreleased/27332-mini-pipeline-graph-with-many-stages-has-no-line-spacing-in-firefox-and-safari.yml
deleted file mode 100644
index 79316abbaf7..00000000000
--- a/changelogs/unreleased/27332-mini-pipeline-graph-with-many-stages-has-no-line-spacing-in-firefox-and-safari.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix pipeline graph vertical spacing in Firefox and Safari
-merge_request: 8886
-author:
diff --git a/changelogs/unreleased/27336-add-environment-url-link-to-terminal-page.yml b/changelogs/unreleased/27336-add-environment-url-link-to-terminal-page.yml
new file mode 100644
index 00000000000..dd4907166c4
--- /dev/null
+++ b/changelogs/unreleased/27336-add-environment-url-link-to-terminal-page.yml
@@ -0,0 +1,4 @@
+---
+title: Added external environment link to web terminal view
+merge_request: 8303
+author:
diff --git a/changelogs/unreleased/27343-autocomplete-post-to-wrong-url-when-not-hosting-in-root.yml b/changelogs/unreleased/27343-autocomplete-post-to-wrong-url-when-not-hosting-in-root.yml
deleted file mode 100644
index 8f061a34ac0..00000000000
--- a/changelogs/unreleased/27343-autocomplete-post-to-wrong-url-when-not-hosting-in-root.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix filtered search user autocomplete for gitlab instances that are hosted
- on a subdirectory
-merge_request: 8891
-author:
diff --git a/changelogs/unreleased/27352-search-label-filter-header.yml b/changelogs/unreleased/27352-search-label-filter-header.yml
deleted file mode 100644
index 191b530aee8..00000000000
--- a/changelogs/unreleased/27352-search-label-filter-header.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: 27352-search-label-filter-header
-merge_request:
-author:
diff --git a/changelogs/unreleased/27452-update-issue-count.yml b/changelogs/unreleased/27452-update-issue-count.yml
new file mode 100644
index 00000000000..a7417eba63c
--- /dev/null
+++ b/changelogs/unreleased/27452-update-issue-count.yml
@@ -0,0 +1,4 @@
+---
+title: update issue count when closing/reopening an issue
+merge_request:
+author:
diff --git a/changelogs/unreleased/27484-environment-show-name.yml b/changelogs/unreleased/27484-environment-show-name.yml
deleted file mode 100644
index dc400d65006..00000000000
--- a/changelogs/unreleased/27484-environment-show-name.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Don't capitalize environment name in show page
-merge_request:
-author:
diff --git a/changelogs/unreleased/27488-fix-jwt-version.yml b/changelogs/unreleased/27488-fix-jwt-version.yml
deleted file mode 100644
index 5135ff0fd60..00000000000
--- a/changelogs/unreleased/27488-fix-jwt-version.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Update and pin the `jwt` gem to ~> 1.5.6
-merge_request:
-author:
diff --git a/changelogs/unreleased/27494-environment-list-column-headers.yml b/changelogs/unreleased/27494-environment-list-column-headers.yml
deleted file mode 100644
index 798c01f3238..00000000000
--- a/changelogs/unreleased/27494-environment-list-column-headers.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Edited the column header for the environments list from created to updated and added created to environments detail page colum header titles
-merge_request:
-author:
diff --git a/changelogs/unreleased/27516-fix-wrong-call-to-project_cache_worker-method.yml b/changelogs/unreleased/27516-fix-wrong-call-to-project_cache_worker-method.yml
deleted file mode 100644
index bc990c66866..00000000000
--- a/changelogs/unreleased/27516-fix-wrong-call-to-project_cache_worker-method.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix wrong call to ProjectCacheWorker.perform
-merge_request: 8910
-author:
diff --git a/changelogs/unreleased/27530-fix-job-dropdown-pipeline-console-error.yml b/changelogs/unreleased/27530-fix-job-dropdown-pipeline-console-error.yml
new file mode 100644
index 00000000000..4436b4bee68
--- /dev/null
+++ b/changelogs/unreleased/27530-fix-job-dropdown-pipeline-console-error.yml
@@ -0,0 +1,4 @@
+---
+title: Fixes job dropdown action throws error in js console
+merge_request: 9182
+author:
diff --git a/changelogs/unreleased/27602-fix-avatar-border-flicker-mention-dropdown.yml b/changelogs/unreleased/27602-fix-avatar-border-flicker-mention-dropdown.yml
deleted file mode 100644
index a5bb37ec8a9..00000000000
--- a/changelogs/unreleased/27602-fix-avatar-border-flicker-mention-dropdown.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixes flickering of avatar border in mention dropdown
-merge_request: 8950
-author:
diff --git a/changelogs/unreleased/27608-fixes-markdown-in-activity-feed-is-light-gray.yml b/changelogs/unreleased/27608-fixes-markdown-in-activity-feed-is-light-gray.yml
new file mode 100644
index 00000000000..8f297620e23
--- /dev/null
+++ b/changelogs/unreleased/27608-fixes-markdown-in-activity-feed-is-light-gray.yml
@@ -0,0 +1,4 @@
+---
+title: Fixes markdown in activity-feed is gray
+merge_request: 9179
+author:
diff --git a/changelogs/unreleased/27610-issue-number-alignment.yml b/changelogs/unreleased/27610-issue-number-alignment.yml
new file mode 100644
index 00000000000..19ab8872c62
--- /dev/null
+++ b/changelogs/unreleased/27610-issue-number-alignment.yml
@@ -0,0 +1,4 @@
+---
+title: fixes issue number alignment problem in MR and issue list
+merge_request: 9020
+author:
diff --git a/changelogs/unreleased/27631-fix-small-height-of-activity-header-page.yml b/changelogs/unreleased/27631-fix-small-height-of-activity-header-page.yml
new file mode 100644
index 00000000000..59da28964f7
--- /dev/null
+++ b/changelogs/unreleased/27631-fix-small-height-of-activity-header-page.yml
@@ -0,0 +1,4 @@
+---
+title: "Fix small height of activity header page"
+merge_request: 8952
+author: Pavel Sorokin
diff --git a/changelogs/unreleased/27632_fix_mr_widget_url.yml b/changelogs/unreleased/27632_fix_mr_widget_url.yml
deleted file mode 100644
index 958621a43a1..00000000000
--- a/changelogs/unreleased/27632_fix_mr_widget_url.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix MR widget url
-merge_request: 8989
-author:
diff --git a/changelogs/unreleased/27639-emoji-panel-under-the-side-panel-in-the-merge-request.yml b/changelogs/unreleased/27639-emoji-panel-under-the-side-panel-in-the-merge-request.yml
deleted file mode 100644
index 0531ef2c038..00000000000
--- a/changelogs/unreleased/27639-emoji-panel-under-the-side-panel-in-the-merge-request.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Layer award emoji dropdown over the right sidebar
-merge_request: 9004
-author:
diff --git a/changelogs/unreleased/27726-fix-dropdown-width-in-admin-project-page.yml b/changelogs/unreleased/27726-fix-dropdown-width-in-admin-project-page.yml
new file mode 100644
index 00000000000..6c98b46d8cb
--- /dev/null
+++ b/changelogs/unreleased/27726-fix-dropdown-width-in-admin-project-page.yml
@@ -0,0 +1,4 @@
+---
+title: Fixes dropdown width in admin project page
+merge_request: 9002
+author:
diff --git a/changelogs/unreleased/27762-add-default-artifacts-expiration.yml b/changelogs/unreleased/27762-add-default-artifacts-expiration.yml
new file mode 100644
index 00000000000..27fa77ed04d
--- /dev/null
+++ b/changelogs/unreleased/27762-add-default-artifacts-expiration.yml
@@ -0,0 +1,4 @@
+---
+title: Add admin setting for default artifacts expiration
+merge_request: 9219
+author:
diff --git a/changelogs/unreleased/27774-text-color-contrast-is-barely-readable-for-pipelines-visualization-graph-page-with-roboto-fonts.yml b/changelogs/unreleased/27774-text-color-contrast-is-barely-readable-for-pipelines-visualization-graph-page-with-roboto-fonts.yml
deleted file mode 100644
index aa89d9f9850..00000000000
--- a/changelogs/unreleased/27774-text-color-contrast-is-barely-readable-for-pipelines-visualization-graph-page-with-roboto-fonts.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Give ci status text on pipeline graph a better font-weight
-merge_request:
-author:
diff --git a/changelogs/unreleased/27783-fix-fe-doc-broken-link.yml b/changelogs/unreleased/27783-fix-fe-doc-broken-link.yml
new file mode 100644
index 00000000000..429110e9178
--- /dev/null
+++ b/changelogs/unreleased/27783-fix-fe-doc-broken-link.yml
@@ -0,0 +1,4 @@
+---
+title: Fixes FE Doc broken link
+merge_request: 9120
+author:
diff --git a/changelogs/unreleased/27822-default-bulk-assign-labels.yml b/changelogs/unreleased/27822-default-bulk-assign-labels.yml
deleted file mode 100644
index ee2431869f0..00000000000
--- a/changelogs/unreleased/27822-default-bulk-assign-labels.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add default labels to bulk assign dropdowns
-merge_request:
-author:
diff --git a/changelogs/unreleased/27920-both-wip-messages-showing.yml b/changelogs/unreleased/27920-both-wip-messages-showing.yml
new file mode 100644
index 00000000000..497fda8c8ba
--- /dev/null
+++ b/changelogs/unreleased/27920-both-wip-messages-showing.yml
@@ -0,0 +1,4 @@
+---
+title: Dispatch needed JS when creating a new MR in diff view
+merge_request:
+author:
diff --git a/changelogs/unreleased/27924-set-max-width-mini-pipeline-text.yml b/changelogs/unreleased/27924-set-max-width-mini-pipeline-text.yml
new file mode 100644
index 00000000000..53077eedc11
--- /dev/null
+++ b/changelogs/unreleased/27924-set-max-width-mini-pipeline-text.yml
@@ -0,0 +1,4 @@
+---
+title: Set maximum width for mini pipeline graph text so it is not truncated to early
+merge_request: 9188
+author:
diff --git a/changelogs/unreleased/27989-disable-counting-tags.yml b/changelogs/unreleased/27989-disable-counting-tags.yml
new file mode 100644
index 00000000000..988785ac454
--- /dev/null
+++ b/changelogs/unreleased/27989-disable-counting-tags.yml
@@ -0,0 +1,4 @@
+---
+title: Disable unused tags count cache for Projects, Builds and Runners
+merge_request:
+author:
diff --git a/changelogs/unreleased/27994-fix-mr-widget-jump.yml b/changelogs/unreleased/27994-fix-mr-widget-jump.yml
new file mode 100644
index 00000000000..77783e54a3a
--- /dev/null
+++ b/changelogs/unreleased/27994-fix-mr-widget-jump.yml
@@ -0,0 +1,4 @@
+---
+title: Fix MR widget jump
+merge_request: 9146
+author:
diff --git a/changelogs/unreleased/28082-deleted-branch-event-404.yml b/changelogs/unreleased/28082-deleted-branch-event-404.yml
new file mode 100644
index 00000000000..e989ca34784
--- /dev/null
+++ b/changelogs/unreleased/28082-deleted-branch-event-404.yml
@@ -0,0 +1,4 @@
+---
+title: Stop linking to deleted Branches in Activity tabs
+merge_request: 9203
+author: Jan Christophersen
diff --git a/changelogs/unreleased/28093-snippet-and-issue-spam-check-on-edit.yml b/changelogs/unreleased/28093-snippet-and-issue-spam-check-on-edit.yml
new file mode 100644
index 00000000000..d70b5ef8fd5
--- /dev/null
+++ b/changelogs/unreleased/28093-snippet-and-issue-spam-check-on-edit.yml
@@ -0,0 +1,4 @@
+---
+title: Spam check and reCAPTCHA improvements
+merge_request:
+author:
diff --git a/changelogs/unreleased/28142-overlap-bugs.yml b/changelogs/unreleased/28142-overlap-bugs.yml
new file mode 100644
index 00000000000..9fdabdf204a
--- /dev/null
+++ b/changelogs/unreleased/28142-overlap-bugs.yml
@@ -0,0 +1,4 @@
+---
+title: Fix z index issues with sidebar
+merge_request:
+author:
diff --git a/changelogs/unreleased/28176_merge_widget_fix.yml b/changelogs/unreleased/28176_merge_widget_fix.yml
new file mode 100644
index 00000000000..8e4e75fc237
--- /dev/null
+++ b/changelogs/unreleased/28176_merge_widget_fix.yml
@@ -0,0 +1,4 @@
+---
+title: Fix error in MR widget after /merge slash command
+merge_request: 9259
+author:
diff --git a/changelogs/unreleased/28186-long-group-names-overflow-out-of-todos-view.yml b/changelogs/unreleased/28186-long-group-names-overflow-out-of-todos-view.yml
new file mode 100644
index 00000000000..3bcf0e06d08
--- /dev/null
+++ b/changelogs/unreleased/28186-long-group-names-overflow-out-of-todos-view.yml
@@ -0,0 +1,4 @@
+---
+title: Truncate long Todo titles for non-mobile screens
+merge_request: 9311
+author:
diff --git a/changelogs/unreleased/28204-option-to-disable-webpack-dev-server-livereload.yml b/changelogs/unreleased/28204-option-to-disable-webpack-dev-server-livereload.yml
new file mode 100644
index 00000000000..df2478a3f28
--- /dev/null
+++ b/changelogs/unreleased/28204-option-to-disable-webpack-dev-server-livereload.yml
@@ -0,0 +1,4 @@
+---
+title: Pick up option from GDK to disable webpack dev server livereload
+merge_request:
+author:
diff --git a/changelogs/unreleased/28229-pipelines-loading-icon.yml b/changelogs/unreleased/28229-pipelines-loading-icon.yml
new file mode 100644
index 00000000000..d8f82f658c2
--- /dev/null
+++ b/changelogs/unreleased/28229-pipelines-loading-icon.yml
@@ -0,0 +1,5 @@
+---
+title: Centers loading icon vertically and horizontally in pipelines table in commit
+ view
+merge_request:
+author:
diff --git a/changelogs/unreleased/28236-browse-button-dropping.yml b/changelogs/unreleased/28236-browse-button-dropping.yml
new file mode 100644
index 00000000000..3a3d755f40c
--- /dev/null
+++ b/changelogs/unreleased/28236-browse-button-dropping.yml
@@ -0,0 +1,4 @@
+---
+title: Increase right side of file header to button stays on same line
+merge_request:
+author:
diff --git a/changelogs/unreleased/28247-timeloops-bug.yml b/changelogs/unreleased/28247-timeloops-bug.yml
new file mode 100644
index 00000000000..12ab523b7c7
--- /dev/null
+++ b/changelogs/unreleased/28247-timeloops-bug.yml
@@ -0,0 +1,4 @@
+---
+title: Only run timeago loops after rendering timeago components
+merge_request:
+author:
diff --git a/changelogs/unreleased/28253-fix-buid-scroll-button-position.yml b/changelogs/unreleased/28253-fix-buid-scroll-button-position.yml
new file mode 100644
index 00000000000..b13d115dab9
--- /dev/null
+++ b/changelogs/unreleased/28253-fix-buid-scroll-button-position.yml
@@ -0,0 +1,4 @@
+---
+title: Fix positioning of `Scroll to top` button
+merge_request:
+author:
diff --git a/changelogs/unreleased/28262-horizontal-scrolling-issue-on-long-project-names.yml b/changelogs/unreleased/28262-horizontal-scrolling-issue-on-long-project-names.yml
new file mode 100644
index 00000000000..fa1674453de
--- /dev/null
+++ b/changelogs/unreleased/28262-horizontal-scrolling-issue-on-long-project-names.yml
@@ -0,0 +1,4 @@
+---
+title: Wrap long Project and Group titles
+merge_request: 9301
+author:
diff --git a/changelogs/unreleased/28303-change-development-tanuki-favicon-colors-to-match-logo.yml b/changelogs/unreleased/28303-change-development-tanuki-favicon-colors-to-match-logo.yml
new file mode 100644
index 00000000000..b97e9a59b2a
--- /dev/null
+++ b/changelogs/unreleased/28303-change-development-tanuki-favicon-colors-to-match-logo.yml
@@ -0,0 +1,4 @@
+---
+title: Change development tanuki favicon colors to match logo color order
+merge_request:
+author:
diff --git a/changelogs/unreleased/28329-allow-slash-in-slash-command-args.yml b/changelogs/unreleased/28329-allow-slash-in-slash-command-args.yml
new file mode 100644
index 00000000000..fed02139a5c
--- /dev/null
+++ b/changelogs/unreleased/28329-allow-slash-in-slash-command-args.yml
@@ -0,0 +1,4 @@
+---
+title: Allow slashes in slash command arguments
+merge_request:
+author:
diff --git a/changelogs/unreleased/28353-little-grammar-issue.yml b/changelogs/unreleased/28353-little-grammar-issue.yml
new file mode 100644
index 00000000000..10bdb17b266
--- /dev/null
+++ b/changelogs/unreleased/28353-little-grammar-issue.yml
@@ -0,0 +1,4 @@
+---
+title: Fix grammer issue in admin/runners
+merge_request:
+author:
diff --git a/changelogs/unreleased/28357-colon-search.yml b/changelogs/unreleased/28357-colon-search.yml
new file mode 100644
index 00000000000..4bbb0dc12b2
--- /dev/null
+++ b/changelogs/unreleased/28357-colon-search.yml
@@ -0,0 +1,4 @@
+---
+title: Allow searching issues for strings containing colons
+merge_request:
+author:
diff --git a/changelogs/unreleased/28389-ux-problem-with-pipeline-coverage-placeholder.yml b/changelogs/unreleased/28389-ux-problem-with-pipeline-coverage-placeholder.yml
new file mode 100644
index 00000000000..ed357d86fe3
--- /dev/null
+++ b/changelogs/unreleased/28389-ux-problem-with-pipeline-coverage-placeholder.yml
@@ -0,0 +1,4 @@
+---
+title: Changed coverage reg expression placeholder text to be more like a placeholder
+merge_request:
+author:
diff --git a/changelogs/unreleased/28450-test-compiling-frontend-assets-for-production-in-ci.yml b/changelogs/unreleased/28450-test-compiling-frontend-assets-for-production-in-ci.yml
new file mode 100644
index 00000000000..196a9b788ea
--- /dev/null
+++ b/changelogs/unreleased/28450-test-compiling-frontend-assets-for-production-in-ci.yml
@@ -0,0 +1,4 @@
+---
+title: test compiling production assets and generate webpack bundle report in CI
+merge_request: 9396
+author:
diff --git a/changelogs/unreleased/28458-present-gitlab-version-for-v4-changes-on-docs.yml b/changelogs/unreleased/28458-present-gitlab-version-for-v4-changes-on-docs.yml
new file mode 100644
index 00000000000..dbbe8a19204
--- /dev/null
+++ b/changelogs/unreleased/28458-present-gitlab-version-for-v4-changes-on-docs.yml
@@ -0,0 +1,4 @@
+---
+title: Present GitLab version for each V3 to V4 API change on v3_to_v4.md
+merge_request:
+author:
diff --git a/changelogs/unreleased/28462-fix-delimiter-removes-issue-in-todo-counter.yml b/changelogs/unreleased/28462-fix-delimiter-removes-issue-in-todo-counter.yml
new file mode 100644
index 00000000000..80995d75c23
--- /dev/null
+++ b/changelogs/unreleased/28462-fix-delimiter-removes-issue-in-todo-counter.yml
@@ -0,0 +1,4 @@
+---
+title: Fixes delimiter removes when todo marked as done
+merge_request: 9435
+author:
diff --git a/changelogs/unreleased/28524-gitlab-ci-yml-coverage-key-is-unknown.yml b/changelogs/unreleased/28524-gitlab-ci-yml-coverage-key-is-unknown.yml
new file mode 100644
index 00000000000..eda5764c13e
--- /dev/null
+++ b/changelogs/unreleased/28524-gitlab-ci-yml-coverage-key-is-unknown.yml
@@ -0,0 +1,4 @@
+---
+title: Document when current coverage configuration option was introduced
+merge_request: 9443
+author:
diff --git a/changelogs/unreleased/3874-correctly-return-json-on-delete-responses.yml b/changelogs/unreleased/3874-correctly-return-json-on-delete-responses.yml
new file mode 100644
index 00000000000..4a4932288b4
--- /dev/null
+++ b/changelogs/unreleased/3874-correctly-return-json-on-delete-responses.yml
@@ -0,0 +1,4 @@
+---
+title: Return 202 with JSON body on async removals on V4 API
+merge_request:
+author:
diff --git a/changelogs/unreleased/395-fix-notification-when-group-set-to-watch.yml b/changelogs/unreleased/395-fix-notification-when-group-set-to-watch.yml
deleted file mode 100644
index 11d1f55172b..00000000000
--- a/changelogs/unreleased/395-fix-notification-when-group-set-to-watch.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix notifications when set at group level
-merge_request: 6813
-author: Alexandre Maia
diff --git a/changelogs/unreleased/8-15-stable.yml b/changelogs/unreleased/8-15-stable.yml
deleted file mode 100644
index 75502e139e7..00000000000
--- a/changelogs/unreleased/8-15-stable.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Ensure export files are removed after a namespace is deleted
-merge_request:
-author:
diff --git a/changelogs/unreleased/8082-permalink-to-file.yml b/changelogs/unreleased/8082-permalink-to-file.yml
deleted file mode 100644
index 136d2108c63..00000000000
--- a/changelogs/unreleased/8082-permalink-to-file.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add `y` keyboard shortcut to move to file permalink
-merge_request:
-author:
diff --git a/changelogs/unreleased/9-0-api-changes.yml b/changelogs/unreleased/9-0-api-changes.yml
deleted file mode 100644
index 2f0f1887257..00000000000
--- a/changelogs/unreleased/9-0-api-changes.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove deprecated MR and Issue endpoints and preserve V3 namespace
-merge_request: 8967
-author:
diff --git a/changelogs/unreleased/9381-authentiq-backchannel-logout.yml b/changelogs/unreleased/9381-authentiq-backchannel-logout.yml
new file mode 100644
index 00000000000..4dbf36cd096
--- /dev/null
+++ b/changelogs/unreleased/9381-authentiq-backchannel-logout.yml
@@ -0,0 +1,4 @@
+---
+title: Adds remote logout functionality to the Authentiq OAuth provider
+merge_request: 9381
+author: Alexandros Keramidas
diff --git a/changelogs/unreleased/Add-a-shash-command-for-target-merge-request-branch.yml b/changelogs/unreleased/Add-a-shash-command-for-target-merge-request-branch.yml
deleted file mode 100644
index 9fd6ea5bc52..00000000000
--- a/changelogs/unreleased/Add-a-shash-command-for-target-merge-request-branch.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Adds /target_branch slash command functionality for merge requests
-merge_request:
-author: YarNayar
diff --git a/changelogs/unreleased/add-auto-submited-header.yml b/changelogs/unreleased/add-auto-submited-header.yml
new file mode 100644
index 00000000000..93481613b39
--- /dev/null
+++ b/changelogs/unreleased/add-auto-submited-header.yml
@@ -0,0 +1,4 @@
+---
+title: Set Auto-Submitted header to mails
+merge_request:
+author: Semyon Pupkov
diff --git a/changelogs/unreleased/add-filtered-search-to-mr.yml b/changelogs/unreleased/add-filtered-search-to-mr.yml
new file mode 100644
index 00000000000..e3577e2aec7
--- /dev/null
+++ b/changelogs/unreleased/add-filtered-search-to-mr.yml
@@ -0,0 +1,4 @@
+---
+title: Add filtered search to MR page
+merge_request:
+author:
diff --git a/changelogs/unreleased/add-issues-tooltip.yml b/changelogs/unreleased/add-issues-tooltip.yml
new file mode 100644
index 00000000000..58adb6c6b5a
--- /dev/null
+++ b/changelogs/unreleased/add-issues-tooltip.yml
@@ -0,0 +1,4 @@
+---
+title: Disabled tooltip on add issues button in usse boards
+merge_request:
+author:
diff --git a/changelogs/unreleased/add-yarn-documentation.yml b/changelogs/unreleased/add-yarn-documentation.yml
new file mode 100644
index 00000000000..5bcc01ac177
--- /dev/null
+++ b/changelogs/unreleased/add-yarn-documentation.yml
@@ -0,0 +1,4 @@
+---
+title: add rake tasks to handle yarn dependencies and update documentation
+merge_request: 9316
+author:
diff --git a/changelogs/unreleased/add_mr_info_to_issues_list.yml b/changelogs/unreleased/add_mr_info_to_issues_list.yml
new file mode 100644
index 00000000000..8087aa6296c
--- /dev/null
+++ b/changelogs/unreleased/add_mr_info_to_issues_list.yml
@@ -0,0 +1,4 @@
+---
+title: Add merge request count to each issue on issues list
+merge_request: 9252
+author: blackst0ne
diff --git a/changelogs/unreleased/add_project_update_hook.yml b/changelogs/unreleased/add_project_update_hook.yml
deleted file mode 100644
index 915c9538843..00000000000
--- a/changelogs/unreleased/add_project_update_hook.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add system hook for when a project is updated (other than rename/transfer)
-merge_request: 5711
-author: Tommy Beadle
diff --git a/changelogs/unreleased/alphabetically_sort_tags_on_runner_list.yml b/changelogs/unreleased/alphabetically_sort_tags_on_runner_list.yml
new file mode 100644
index 00000000000..ffcf197a596
--- /dev/null
+++ b/changelogs/unreleased/alphabetically_sort_tags_on_runner_list.yml
@@ -0,0 +1,4 @@
+---
+title: Alphabetically sort tags on runner list
+merge_request: 8922
+author: blackst0ne
diff --git a/changelogs/unreleased/api-entities.yml b/changelogs/unreleased/api-entities.yml
new file mode 100644
index 00000000000..2003d00fd52
--- /dev/null
+++ b/changelogs/unreleased/api-entities.yml
@@ -0,0 +1,4 @@
+---
+title: "Use an entity for RepoBranch commits and enhance RepoCommit"
+merge_request: 7138
+author: Ben Boeckel
diff --git a/changelogs/unreleased/api-fix-files.yml b/changelogs/unreleased/api-fix-files.yml
deleted file mode 100644
index 8a9e29109a8..00000000000
--- a/changelogs/unreleased/api-fix-files.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: 'API: Fix file downloading'
-merge_request: Robert Schilling
-author: 8267
diff --git a/changelogs/unreleased/api-notes-entity-fields.yml b/changelogs/unreleased/api-notes-entity-fields.yml
new file mode 100644
index 00000000000..f7631df31e2
--- /dev/null
+++ b/changelogs/unreleased/api-notes-entity-fields.yml
@@ -0,0 +1,4 @@
+---
+title: 'API: Remove deprecated fields Notes#upvotes and Notes#downvotes'
+merge_request: 9384
+author: Robert Schilling
diff --git a/changelogs/unreleased/api-post-block.yml b/changelogs/unreleased/api-post-block.yml
new file mode 100644
index 00000000000..dfc61ffa9e3
--- /dev/null
+++ b/changelogs/unreleased/api-post-block.yml
@@ -0,0 +1,4 @@
+---
+title: 'API: Use POST to (un)block a user'
+merge_request: 9371
+author: Robert Schilling
diff --git a/changelogs/unreleased/api-remove-deploy-key-disable.yml b/changelogs/unreleased/api-remove-deploy-key-disable.yml
new file mode 100644
index 00000000000..f471ad2aa20
--- /dev/null
+++ b/changelogs/unreleased/api-remove-deploy-key-disable.yml
@@ -0,0 +1,4 @@
+---
+title: 'API: Remove `DELETE projects/:id/deploy_keys/:key_id/disable`'
+merge_request: 9365
+author: Robert Schilling
diff --git a/changelogs/unreleased/api-remove-owned-groups.yml b/changelogs/unreleased/api-remove-owned-groups.yml
new file mode 100644
index 00000000000..cf0301b7fe0
--- /dev/null
+++ b/changelogs/unreleased/api-remove-owned-groups.yml
@@ -0,0 +1,4 @@
+---
+title: 'API: Remove /groups/owned endpoint'
+merge_request: 9505
+author: Robert Schilling
diff --git a/changelogs/unreleased/api-remove-snippets-expires-at.yml b/changelogs/unreleased/api-remove-snippets-expires-at.yml
deleted file mode 100644
index 67603bfab3b..00000000000
--- a/changelogs/unreleased/api-remove-snippets-expires-at.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: 'API: Remove deprecated ''expires_at'' from project snippets'
-merge_request: 8723
-author: Robert Schilling
diff --git a/changelogs/unreleased/api-star-restful.yml b/changelogs/unreleased/api-star-restful.yml
new file mode 100644
index 00000000000..3e7de8cd822
--- /dev/null
+++ b/changelogs/unreleased/api-star-restful.yml
@@ -0,0 +1,4 @@
+---
+title: 'API: Moved `DELETE /projects/:id/star` to `POST /projects/:id/unstar`'
+merge_request: 9328
+author: Robert Schilling
diff --git a/changelogs/unreleased/api-subscription-restful.yml b/changelogs/unreleased/api-subscription-restful.yml
new file mode 100644
index 00000000000..95db470e6c9
--- /dev/null
+++ b/changelogs/unreleased/api-subscription-restful.yml
@@ -0,0 +1,4 @@
+---
+title: 'API: - Make subscription API more RESTful. Use `post ":project_id/:subscribable_type/:subscribable_id/subscribe"` to subscribe and `post ":project_id/:subscribable_type/:subscribable_id/unsubscribe"` to unsubscribe from a resource.'
+merge_request: 9325
+author: Robert Schilling
diff --git a/changelogs/unreleased/api-todos-restful.yml b/changelogs/unreleased/api-todos-restful.yml
new file mode 100644
index 00000000000..dba1350a495
--- /dev/null
+++ b/changelogs/unreleased/api-todos-restful.yml
@@ -0,0 +1,4 @@
+---
+title: 'API: Use POST requests to mark todos as done'
+merge_request: 9410
+author: Robert Schilling
diff --git a/changelogs/unreleased/artifactsdoc.yml b/changelogs/unreleased/artifactsdoc.yml
new file mode 100644
index 00000000000..4ef32d5256f
--- /dev/null
+++ b/changelogs/unreleased/artifactsdoc.yml
@@ -0,0 +1,4 @@
+---
+title: Added documentation for permalinks to most recent build artifacts.
+merge_request: 8934
+author: Christian Godenschwager
diff --git a/changelogs/unreleased/babel-all-the-things.yml b/changelogs/unreleased/babel-all-the-things.yml
deleted file mode 100644
index fda1c3bd562..00000000000
--- a/changelogs/unreleased/babel-all-the-things.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: use babel to transpile all non-vendor javascript assets regardless of file
- extension
-merge_request: 8988
-author:
diff --git a/changelogs/unreleased/beautiful-karma-output.yml b/changelogs/unreleased/beautiful-karma-output.yml
new file mode 100644
index 00000000000..6ccddebab68
--- /dev/null
+++ b/changelogs/unreleased/beautiful-karma-output.yml
@@ -0,0 +1,4 @@
+---
+title: Make Karma output look nicer for CI
+merge_request: 9165
+author: winniehell
diff --git a/changelogs/unreleased/branch_deletion.yml b/changelogs/unreleased/branch_deletion.yml
new file mode 100644
index 00000000000..dbc9265a1fb
--- /dev/null
+++ b/changelogs/unreleased/branch_deletion.yml
@@ -0,0 +1,4 @@
+---
+title: on branch deletion show loading icon and disabled the button
+merge_request: 6761
+author: wendy0402
diff --git a/changelogs/unreleased/broken_iamge_when_doing_offline_update_check-help_page-.yml b/changelogs/unreleased/broken_iamge_when_doing_offline_update_check-help_page-.yml
deleted file mode 100644
index 77750b55e7e..00000000000
--- a/changelogs/unreleased/broken_iamge_when_doing_offline_update_check-help_page-.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Hide version check image if there is no internet connection
-merge_request: 8355
-author: Ken Ding
diff --git a/changelogs/unreleased/bypass-email-domain-validation-when-created-by-admin.yml b/changelogs/unreleased/bypass-email-domain-validation-when-created-by-admin.yml
new file mode 100644
index 00000000000..f335ae27fda
--- /dev/null
+++ b/changelogs/unreleased/bypass-email-domain-validation-when-created-by-admin.yml
@@ -0,0 +1,4 @@
+---
+title: Bypass email domain validation when a user is created by an admin.
+merge_request: 8575
+author: Reza Mohammadi @remohammadi
diff --git a/changelogs/unreleased/clipboard-button-commit-sha.yml b/changelogs/unreleased/clipboard-button-commit-sha.yml
deleted file mode 100644
index 6aa4a5664e7..00000000000
--- a/changelogs/unreleased/clipboard-button-commit-sha.yml
+++ /dev/null
@@ -1,3 +0,0 @@
----
-title: 'Copy commit SHA to clipboard'
-merge_request: 8547
diff --git a/changelogs/unreleased/commit-search-ui-fix.yml b/changelogs/unreleased/commit-search-ui-fix.yml
new file mode 100644
index 00000000000..4a5c2cf6090
--- /dev/null
+++ b/changelogs/unreleased/commit-search-ui-fix.yml
@@ -0,0 +1,4 @@
+---
+title: Fixed commit search UI
+merge_request:
+author:
diff --git a/changelogs/unreleased/contribution-calendar-scroll.yml b/changelogs/unreleased/contribution-calendar-scroll.yml
deleted file mode 100644
index a504d59e61c..00000000000
--- a/changelogs/unreleased/contribution-calendar-scroll.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: contribution calendar scrolls from right to left
-merge_request:
-author:
diff --git a/changelogs/unreleased/cop-gem-fetcher.yml b/changelogs/unreleased/cop-gem-fetcher.yml
deleted file mode 100644
index 506815a5b54..00000000000
--- a/changelogs/unreleased/cop-gem-fetcher.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Cop for gem fetched from a git source
-merge_request: 8856
-author: Adam Pahlevi
diff --git a/changelogs/unreleased/copy-as-md.yml b/changelogs/unreleased/copy-as-md.yml
deleted file mode 100644
index 637e9dc36e2..00000000000
--- a/changelogs/unreleased/copy-as-md.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Copying a rendered issue/comment will paste into GFM textareas as actual GFM
-merge_request:
-author:
diff --git a/changelogs/unreleased/copy-branch-to-clipboard.yml b/changelogs/unreleased/copy-branch-to-clipboard.yml
new file mode 100644
index 00000000000..c12e324ed3c
--- /dev/null
+++ b/changelogs/unreleased/copy-branch-to-clipboard.yml
@@ -0,0 +1,4 @@
+---
+title: Added the ability to copy a branch name to the clipboard
+merge_request: 9103
+author: Glenn Sayers
diff --git a/changelogs/unreleased/cover-my-karma.yml b/changelogs/unreleased/cover-my-karma.yml
new file mode 100644
index 00000000000..4a823dc5ca4
--- /dev/null
+++ b/changelogs/unreleased/cover-my-karma.yml
@@ -0,0 +1,4 @@
+---
+title: Reintroduce coverage report for JavaScript
+merge_request: 9133
+author: winniehell
diff --git a/changelogs/unreleased/create_branch_repo_less.yml b/changelogs/unreleased/create_branch_repo_less.yml
new file mode 100644
index 00000000000..e8b14fa3b67
--- /dev/null
+++ b/changelogs/unreleased/create_branch_repo_less.yml
@@ -0,0 +1,4 @@
+---
+title: Creating a new branch from an issue will automatically initialize a repository if one doesn't already exist.
+merge_request:
+author:
diff --git a/changelogs/unreleased/disable-autologin-on-email-confirmation-links.yml b/changelogs/unreleased/disable-autologin-on-email-confirmation-links.yml
deleted file mode 100644
index 6dd0d748001..00000000000
--- a/changelogs/unreleased/disable-autologin-on-email-confirmation-links.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Disable automatic login after clicking email confirmation links
-merge_request: 7472
-author:
diff --git a/changelogs/unreleased/display-project-id.yml b/changelogs/unreleased/display-project-id.yml
deleted file mode 100644
index 8705ed28400..00000000000
--- a/changelogs/unreleased/display-project-id.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Display project ID in project settings
-merge_request: 8572
-author: winniehell
diff --git a/changelogs/unreleased/document-how-to-vue.yml b/changelogs/unreleased/document-how-to-vue.yml
deleted file mode 100644
index 863e41b6413..00000000000
--- a/changelogs/unreleased/document-how-to-vue.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Adds documentation for how to use Vue.js
-merge_request: 8866
-author:
diff --git a/changelogs/unreleased/dont-delete-assigned-issuables.yml b/changelogs/unreleased/dont-delete-assigned-issuables.yml
deleted file mode 100644
index fb589a053c0..00000000000
--- a/changelogs/unreleased/dont-delete-assigned-issuables.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Don't delete assigned MRs/issues when user is deleted
-merge_request:
-author:
diff --git a/changelogs/unreleased/dynamic-header-fixture.yml b/changelogs/unreleased/dynamic-header-fixture.yml
new file mode 100644
index 00000000000..9789a1999c8
--- /dev/null
+++ b/changelogs/unreleased/dynamic-header-fixture.yml
@@ -0,0 +1,4 @@
+---
+title: Replace static fixture for header_spec.js
+merge_request: 9174
+author: winniehell
diff --git a/changelogs/unreleased/dynamic-project-title-fixture.yml b/changelogs/unreleased/dynamic-project-title-fixture.yml
new file mode 100644
index 00000000000..2404cbb891c
--- /dev/null
+++ b/changelogs/unreleased/dynamic-project-title-fixture.yml
@@ -0,0 +1,4 @@
+---
+title: Replace static fixture for project_title_spec.js
+merge_request: 9175
+author: winniehell
diff --git a/changelogs/unreleased/dz-create-nested-groups-via-ui.yml b/changelogs/unreleased/dz-create-nested-groups-via-ui.yml
new file mode 100644
index 00000000000..f9529a5941a
--- /dev/null
+++ b/changelogs/unreleased/dz-create-nested-groups-via-ui.yml
@@ -0,0 +1,4 @@
+---
+title: Allow creating nested groups via UI
+merge_request: 8786
+author:
diff --git a/changelogs/unreleased/dz-nested-groups-api.yml b/changelogs/unreleased/dz-nested-groups-api.yml
new file mode 100644
index 00000000000..d33ff42700f
--- /dev/null
+++ b/changelogs/unreleased/dz-nested-groups-api.yml
@@ -0,0 +1,4 @@
+---
+title: Add nested groups to the API
+merge_request: 9034
+author:
diff --git a/changelogs/unreleased/dz-nested-groups-improvements-2.yml b/changelogs/unreleased/dz-nested-groups-improvements-2.yml
deleted file mode 100644
index 8e4eb7f1fff..00000000000
--- a/changelogs/unreleased/dz-nested-groups-improvements-2.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add read-only full_path and full_name attributes to Group API
-merge_request: 8827
-author:
diff --git a/changelogs/unreleased/dz-refactor-full-path.yml b/changelogs/unreleased/dz-refactor-full-path.yml
new file mode 100644
index 00000000000..da8568fd220
--- /dev/null
+++ b/changelogs/unreleased/dz-refactor-full-path.yml
@@ -0,0 +1,4 @@
+---
+title: Store group and project full name and full path in routes table
+merge_request: 8979
+author:
diff --git a/changelogs/unreleased/empty-selection-reply-shortcut.yml b/changelogs/unreleased/empty-selection-reply-shortcut.yml
deleted file mode 100644
index 5a42c98a800..00000000000
--- a/changelogs/unreleased/empty-selection-reply-shortcut.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Change the reply shortcut to focus the field even without a selection.
-merge_request: 8873
-author: Brian Hall
diff --git a/changelogs/unreleased/fe-commit-mr-pipelines.yml b/changelogs/unreleased/fe-commit-mr-pipelines.yml
deleted file mode 100644
index b5cc6bbf8b6..00000000000
--- a/changelogs/unreleased/fe-commit-mr-pipelines.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Use vue.js Pipelines table in commit and merge request view
-merge_request: 8844
-author:
diff --git a/changelogs/unreleased/fe-paginated-environments-api-add-subview.yml b/changelogs/unreleased/fe-paginated-environments-api-add-subview.yml
new file mode 100644
index 00000000000..7e626982de6
--- /dev/null
+++ b/changelogs/unreleased/fe-paginated-environments-api-add-subview.yml
@@ -0,0 +1,4 @@
+---
+title: Adds paginationd and folders view to environments table
+merge_request:
+author:
diff --git a/changelogs/unreleased/feature-brand-logo-in-emails.yml b/changelogs/unreleased/feature-brand-logo-in-emails.yml
new file mode 100644
index 00000000000..a7674b9b25e
--- /dev/null
+++ b/changelogs/unreleased/feature-brand-logo-in-emails.yml
@@ -0,0 +1,4 @@
+---
+title: Brand header logo for pipeline emails
+merge_request: 9049
+author: Alexis Reigel
diff --git a/changelogs/unreleased/feature-github-find-users-by-email.yml b/changelogs/unreleased/feature-github-find-users-by-email.yml
new file mode 100644
index 00000000000..1503cf2b9f7
--- /dev/null
+++ b/changelogs/unreleased/feature-github-find-users-by-email.yml
@@ -0,0 +1,4 @@
+---
+title: GitHub Importer - Find users based on GitHub email address
+merge_request: 8958
+author:
diff --git a/changelogs/unreleased/feature-success-warning-icons-in-stages-builds.yml b/changelogs/unreleased/feature-success-warning-icons-in-stages-builds.yml
deleted file mode 100644
index 5fba0332881..00000000000
--- a/changelogs/unreleased/feature-success-warning-icons-in-stages-builds.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Use warning icon in mini-graph if stage passed conditionally
-merge_request: 8503
-author:
diff --git a/changelogs/unreleased/fix-27479.yml b/changelogs/unreleased/fix-27479.yml
deleted file mode 100644
index cc72a830695..00000000000
--- a/changelogs/unreleased/fix-27479.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove new branch button for confidential issues
-merge_request:
-author:
diff --git a/changelogs/unreleased/fix-anchor-scrolling.yml b/changelogs/unreleased/fix-anchor-scrolling.yml
deleted file mode 100644
index 43b3b9bf96e..00000000000
--- a/changelogs/unreleased/fix-anchor-scrolling.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix broken anchor links when special characters are used
-merge_request: 8961
-author: Andrey Krivko
diff --git a/changelogs/unreleased/fix-api-mr-permissions.yml b/changelogs/unreleased/fix-api-mr-permissions.yml
deleted file mode 100644
index 33b677b1f29..00000000000
--- a/changelogs/unreleased/fix-api-mr-permissions.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Don't allow project guests to subscribe to merge requests through the API
-merge_request:
-author: Robert Schilling
diff --git a/changelogs/unreleased/fix-ci-build-policy.yml b/changelogs/unreleased/fix-ci-build-policy.yml
deleted file mode 100644
index 26003713ed4..00000000000
--- a/changelogs/unreleased/fix-ci-build-policy.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Improve build policy and access abilities
-merge_request: 8711
-author:
diff --git a/changelogs/unreleased/fix-cycle-analytics-events-limit.yml b/changelogs/unreleased/fix-cycle-analytics-events-limit.yml
new file mode 100644
index 00000000000..152b37ca430
--- /dev/null
+++ b/changelogs/unreleased/fix-cycle-analytics-events-limit.yml
@@ -0,0 +1,4 @@
+---
+title: Add limit to the number of events showed in cycle analytics
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-deleting-project-again.yml b/changelogs/unreleased/fix-deleting-project-again.yml
deleted file mode 100644
index e13215f22a7..00000000000
--- a/changelogs/unreleased/fix-deleting-project-again.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix deleting projects with pipelines and builds
-merge_request: 8960
-author:
diff --git a/changelogs/unreleased/fix-depr-warn.yml b/changelogs/unreleased/fix-depr-warn.yml
deleted file mode 100644
index 61817027720..00000000000
--- a/changelogs/unreleased/fix-depr-warn.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: resolve deprecation warnings
-merge_request: 8855
-author: Adam Pahlevi
diff --git a/changelogs/unreleased/fix-gb-backwards-compatibility-coverage-ci-yml.yml b/changelogs/unreleased/fix-gb-backwards-compatibility-coverage-ci-yml.yml
deleted file mode 100644
index df7e3776700..00000000000
--- a/changelogs/unreleased/fix-gb-backwards-compatibility-coverage-ci-yml.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Preserve backward compatibility CI/CD and disallow setting `coverage` regexp in global context
-merge_request: 8981
-author:
diff --git a/changelogs/unreleased/fix-gb-notification-settings-when-no-repository.yml b/changelogs/unreleased/fix-gb-notification-settings-when-no-repository.yml
new file mode 100644
index 00000000000..17fd1336b8e
--- /dev/null
+++ b/changelogs/unreleased/fix-gb-notification-settings-when-no-repository.yml
@@ -0,0 +1,4 @@
+---
+title: Show notifications settings dropdown even if repository feature is disabled
+merge_request: 9180
+author:
diff --git a/changelogs/unreleased/fix-gb-pipeline-retry-builds-started.yml b/changelogs/unreleased/fix-gb-pipeline-retry-builds-started.yml
new file mode 100644
index 00000000000..49e243ca6bb
--- /dev/null
+++ b/changelogs/unreleased/fix-gb-pipeline-retry-builds-started.yml
@@ -0,0 +1,4 @@
+---
+title: Fix CI/CD pipeline retry and take stages order into account
+merge_request: 9021
+author:
diff --git a/changelogs/unreleased/fix-gb-pipeline-retry-cancel-buttons-consistency.yml b/changelogs/unreleased/fix-gb-pipeline-retry-cancel-buttons-consistency.yml
new file mode 100644
index 00000000000..d747e0e63a3
--- /dev/null
+++ b/changelogs/unreleased/fix-gb-pipeline-retry-cancel-buttons-consistency.yml
@@ -0,0 +1,4 @@
+---
+title: Fix pipeline retry and cancel buttons on pipeline details page
+merge_request: 9225
+author:
diff --git a/changelogs/unreleased/fix-guest-access-posting-to-notes.yml b/changelogs/unreleased/fix-guest-access-posting-to-notes.yml
deleted file mode 100644
index 81377c0c6f0..00000000000
--- a/changelogs/unreleased/fix-guest-access-posting-to-notes.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Prevent users from creating notes on resources they can't access
-merge_request:
-author:
diff --git a/changelogs/unreleased/fix-import-encrypt-atts.yml b/changelogs/unreleased/fix-import-encrypt-atts.yml
deleted file mode 100644
index e34d895570b..00000000000
--- a/changelogs/unreleased/fix-import-encrypt-atts.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Ignore encrypted attributes in Import/Export
-merge_request:
-author:
diff --git a/changelogs/unreleased/fix-import-group-members.yml b/changelogs/unreleased/fix-import-group-members.yml
deleted file mode 100644
index fe580af31b3..00000000000
--- a/changelogs/unreleased/fix-import-group-members.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add ability to export project inherited group members to Import/Export
-merge_request: 8923
-author:
diff --git a/changelogs/unreleased/fix-mr-size-with-over-100-files.yml b/changelogs/unreleased/fix-mr-size-with-over-100-files.yml
new file mode 100644
index 00000000000..eecf3c99a75
--- /dev/null
+++ b/changelogs/unreleased/fix-mr-size-with-over-100-files.yml
@@ -0,0 +1,4 @@
+---
+title: Fix MR changes tab size count when there are over 100 files in the diff
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-references-header-parsing.yml b/changelogs/unreleased/fix-references-header-parsing.yml
deleted file mode 100644
index b927279cdf4..00000000000
--- a/changelogs/unreleased/fix-references-header-parsing.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix reply by email without sub-addressing for some clients from
- Microsoft and Apple
-merge_request: 8620
-author:
diff --git a/changelogs/unreleased/fix-scroll-test.yml b/changelogs/unreleased/fix-scroll-test.yml
deleted file mode 100644
index e98ac755b88..00000000000
--- a/changelogs/unreleased/fix-scroll-test.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Change rspec test to guarantee window is resized before visiting page
-merge_request:
-author:
diff --git a/changelogs/unreleased/fix-users-deleting-public-deployment-keys.yml b/changelogs/unreleased/fix-users-deleting-public-deployment-keys.yml
deleted file mode 100644
index c9edd1de86c..00000000000
--- a/changelogs/unreleased/fix-users-deleting-public-deployment-keys.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Prevent users from deleting system deploy keys via the project deploy key API
-merge_request:
-author:
diff --git a/changelogs/unreleased/fix_broken_diff_discussions.yml b/changelogs/unreleased/fix_broken_diff_discussions.yml
deleted file mode 100644
index 4551212759f..00000000000
--- a/changelogs/unreleased/fix_broken_diff_discussions.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Make MR-review-discussions more reliable
-merge_request:
-author:
diff --git a/changelogs/unreleased/fix_issue_from_milestone.yml b/changelogs/unreleased/fix_issue_from_milestone.yml
new file mode 100644
index 00000000000..02581e3ea09
--- /dev/null
+++ b/changelogs/unreleased/fix_issue_from_milestone.yml
@@ -0,0 +1,4 @@
+---
+title: fix milestone does not automatically assign when create issue from milestone
+merge_request:
+author:
diff --git a/changelogs/unreleased/fixes-namespace-api-documentation.yml b/changelogs/unreleased/fixes-namespace-api-documentation.yml
new file mode 100644
index 00000000000..6b578bb1602
--- /dev/null
+++ b/changelogs/unreleased/fixes-namespace-api-documentation.yml
@@ -0,0 +1,4 @@
+---
+title: Update API docs for new namespace format
+merge_request: 9073
+author: Markus Koller
diff --git a/changelogs/unreleased/fwn-to-find-by-full-path.yml b/changelogs/unreleased/fwn-to-find-by-full-path.yml
deleted file mode 100644
index 1427e4e7624..00000000000
--- a/changelogs/unreleased/fwn-to-find-by-full-path.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: replace `find_with_namespace` with `find_by_full_path`
-merge_request: 8949
-author: Adam Pahlevi
diff --git a/changelogs/unreleased/get-rid-of-water-from-notification_service_spec-to-make-it-DRY.yml b/changelogs/unreleased/get-rid-of-water-from-notification_service_spec-to-make-it-DRY.yml
deleted file mode 100644
index f60417d185e..00000000000
--- a/changelogs/unreleased/get-rid-of-water-from-notification_service_spec-to-make-it-DRY.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Make notification_service spec DRYer by making test reusable
-merge_request:
-author: YarNayar
diff --git a/changelogs/unreleased/gfm-autocomplete-fixes.yml b/changelogs/unreleased/gfm-autocomplete-fixes.yml
new file mode 100644
index 00000000000..737e2ad5234
--- /dev/null
+++ b/changelogs/unreleased/gfm-autocomplete-fixes.yml
@@ -0,0 +1,4 @@
+---
+title: Fix errors in slash commands matcher, add simple test coverage
+merge_request:
+author: YarNayar
diff --git a/changelogs/unreleased/git_to_html_redirection.yml b/changelogs/unreleased/git_to_html_redirection.yml
deleted file mode 100644
index b2959c02c07..00000000000
--- a/changelogs/unreleased/git_to_html_redirection.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Redirect http://someproject.git to http://someproject
-merge_request:
-author: blackst0ne
diff --git a/changelogs/unreleased/go-go-gadget-webpack.yml b/changelogs/unreleased/go-go-gadget-webpack.yml
deleted file mode 100644
index 7f372ccb428..00000000000
--- a/changelogs/unreleased/go-go-gadget-webpack.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: use webpack to bundle frontend assets and use karma for frontend testing
-merge_request: 7288
-author:
diff --git a/changelogs/unreleased/group-label-sidebar-link.yml b/changelogs/unreleased/group-label-sidebar-link.yml
deleted file mode 100644
index c11c2d4ede1..00000000000
--- a/changelogs/unreleased/group-label-sidebar-link.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixed group label links in issue/merge request sidebar
-merge_request:
-author:
diff --git a/changelogs/unreleased/group-memebrs-owner-level.yml b/changelogs/unreleased/group-memebrs-owner-level.yml
new file mode 100644
index 00000000000..ba77f38eb6d
--- /dev/null
+++ b/changelogs/unreleased/group-memebrs-owner-level.yml
@@ -0,0 +1,4 @@
+---
+title: Added option to update to owner for group members
+merge_request:
+author:
diff --git a/changelogs/unreleased/hardcode-title-system-note.yml b/changelogs/unreleased/hardcode-title-system-note.yml
deleted file mode 100644
index 1b0a63efa51..00000000000
--- a/changelogs/unreleased/hardcode-title-system-note.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Ensure autogenerated title does not cause failing spec
-merge_request: 8963
-author: brian m. carlson
diff --git a/changelogs/unreleased/improve-ci-example-php-doc.yml b/changelogs/unreleased/improve-ci-example-php-doc.yml
deleted file mode 100644
index 39a85e3d261..00000000000
--- a/changelogs/unreleased/improve-ci-example-php-doc.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Changed composer installer script in the CI PHP example doc
-merge_request: 4342
-author: Jeffrey Cafferata
diff --git a/changelogs/unreleased/improve-handleLocationHash-tests.yml b/changelogs/unreleased/improve-handleLocationHash-tests.yml
deleted file mode 100644
index 8ae3dfe079c..00000000000
--- a/changelogs/unreleased/improve-handleLocationHash-tests.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Improve gl.utils.handleLocationHash tests
-merge_request:
-author:
diff --git a/changelogs/unreleased/instrument-in-karma.yml b/changelogs/unreleased/instrument-in-karma.yml
new file mode 100644
index 00000000000..cfabf2569fe
--- /dev/null
+++ b/changelogs/unreleased/instrument-in-karma.yml
@@ -0,0 +1,4 @@
+---
+title: Move babel config for instanbul to karma config
+merge_request: 9286
+author: winniehell
diff --git a/changelogs/unreleased/issuable-sidebar-bug.yml b/changelogs/unreleased/issuable-sidebar-bug.yml
deleted file mode 100644
index 4086292eb89..00000000000
--- a/changelogs/unreleased/issuable-sidebar-bug.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixed Issuable sidebar not closing on smaller/mobile sized screens
-merge_request:
-author:
diff --git a/changelogs/unreleased/issue-20428.yml b/changelogs/unreleased/issue-20428.yml
deleted file mode 100644
index 60da1c14702..00000000000
--- a/changelogs/unreleased/issue-20428.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add ability to define a coverage regex in the .gitlab-ci.yml
-merge_request: 7447
-author: Leandro Camargo
diff --git a/changelogs/unreleased/issue-newproj-layout.yml b/changelogs/unreleased/issue-newproj-layout.yml
new file mode 100644
index 00000000000..d15e8b7d1e5
--- /dev/null
+++ b/changelogs/unreleased/issue-newproj-layout.yml
@@ -0,0 +1,4 @@
+---
+title: Removed duplicate "Visibility Level" label on New Project page
+merge_request:
+author: Robert Marcano
diff --git a/changelogs/unreleased/issue-sidebar-empty-assignee.yml b/changelogs/unreleased/issue-sidebar-empty-assignee.yml
deleted file mode 100644
index 263af75b9e9..00000000000
--- a/changelogs/unreleased/issue-sidebar-empty-assignee.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Resets assignee dropdown when sidebar is open
-merge_request:
-author:
diff --git a/changelogs/unreleased/issue_19262.yml b/changelogs/unreleased/issue_19262.yml
deleted file mode 100644
index 5dea1493f23..00000000000
--- a/changelogs/unreleased/issue_19262.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Disallow system notes for closed issuables
-merge_request:
-author:
diff --git a/changelogs/unreleased/issue_25112.yml b/changelogs/unreleased/issue_25112.yml
new file mode 100644
index 00000000000..c43d2732b9a
--- /dev/null
+++ b/changelogs/unreleased/issue_25112.yml
@@ -0,0 +1,4 @@
+---
+title: Disable invalid service templates
+merge_request:
+author:
diff --git a/changelogs/unreleased/issue_25900.yml b/changelogs/unreleased/issue_25900.yml
new file mode 100644
index 00000000000..b4b72b8a20c
--- /dev/null
+++ b/changelogs/unreleased/issue_25900.yml
@@ -0,0 +1,4 @@
+---
+title: Gather issuable metadata to avoid n+1 queries on index view
+merge_request:
+author:
diff --git a/changelogs/unreleased/issue_26701.yml b/changelogs/unreleased/issue_26701.yml
new file mode 100644
index 00000000000..6834351bf43
--- /dev/null
+++ b/changelogs/unreleased/issue_26701.yml
@@ -0,0 +1,4 @@
+---
+title: Remove JIRA closed status icon
+merge_request:
+author:
diff --git a/changelogs/unreleased/issue_27211.yml b/changelogs/unreleased/issue_27211.yml
deleted file mode 100644
index ad48fec5d85..00000000000
--- a/changelogs/unreleased/issue_27211.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove unused js response from refs controller
-merge_request:
-author:
diff --git a/changelogs/unreleased/issue_28051_2.yml b/changelogs/unreleased/issue_28051_2.yml
new file mode 100644
index 00000000000..8cc32ad8493
--- /dev/null
+++ b/changelogs/unreleased/issue_28051_2.yml
@@ -0,0 +1,4 @@
+---
+title: Use default branch as target_branch when parameter is missing
+merge_request:
+author:
diff --git a/changelogs/unreleased/jej-pages-picked-from-ee.yml b/changelogs/unreleased/jej-pages-picked-from-ee.yml
deleted file mode 100644
index ee4a43a93db..00000000000
--- a/changelogs/unreleased/jej-pages-picked-from-ee.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Added GitLab Pages to CE
-merge_request: 8463
-author:
diff --git a/changelogs/unreleased/label-promotion.yml b/changelogs/unreleased/label-promotion.yml
deleted file mode 100644
index 2ab997bf420..00000000000
--- a/changelogs/unreleased/label-promotion.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: "Project labels can now be promoted to group labels"
-merge_request: 7242
-author: Olaf Tomalka
diff --git a/changelogs/unreleased/lnovy-gitlab-ce-empty-variables.yml b/changelogs/unreleased/lnovy-gitlab-ce-empty-variables.yml
new file mode 100644
index 00000000000..bd5db5ac7af
--- /dev/null
+++ b/changelogs/unreleased/lnovy-gitlab-ce-empty-variables.yml
@@ -0,0 +1,4 @@
+---
+title: 'UI: Allow a project variable to be set to an empty value'
+merge_request: 6044
+author: Lukáš Nový
diff --git a/changelogs/unreleased/markdown-plantuml.yml b/changelogs/unreleased/markdown-plantuml.yml
deleted file mode 100644
index c855f0cbcf7..00000000000
--- a/changelogs/unreleased/markdown-plantuml.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: PlantUML support for Markdown
-merge_request: 8588
-author: Horacio Sanson
diff --git a/changelogs/unreleased/misalinged-discussion-with-no-avatar-26854.yml b/changelogs/unreleased/misalinged-discussion-with-no-avatar-26854.yml
deleted file mode 100644
index f32b3aea3c8..00000000000
--- a/changelogs/unreleased/misalinged-discussion-with-no-avatar-26854.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: adds avatar for discussion note
-merge_request: 8734
-author:
diff --git a/changelogs/unreleased/move_tags_service_to_namespace.yml b/changelogs/unreleased/move_tags_service_to_namespace.yml
new file mode 100644
index 00000000000..ba76f291162
--- /dev/null
+++ b/changelogs/unreleased/move_tags_service_to_namespace.yml
@@ -0,0 +1,4 @@
+---
+title: Move tag services to Tags namespace
+merge_request:
+author: dixpac
diff --git a/changelogs/unreleased/mr-tabs-container-offset.yml b/changelogs/unreleased/mr-tabs-container-offset.yml
deleted file mode 100644
index c5df8abfcf2..00000000000
--- a/changelogs/unreleased/mr-tabs-container-offset.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixed merge requests tab extra margin when fixed to window
-merge_request:
-author:
diff --git a/changelogs/unreleased/new-branch-fixture.yml b/changelogs/unreleased/new-branch-fixture.yml
new file mode 100644
index 00000000000..ce5ed816102
--- /dev/null
+++ b/changelogs/unreleased/new-branch-fixture.yml
@@ -0,0 +1,4 @@
+---
+title: Replace static fixture for new_branch_spec.js
+merge_request: 9131
+author: winniehell
diff --git a/changelogs/unreleased/newline-eslint-rule.yml b/changelogs/unreleased/newline-eslint-rule.yml
deleted file mode 100644
index 5ce080b6912..00000000000
--- a/changelogs/unreleased/newline-eslint-rule.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Flag multiple empty lines in eslint, fix offenses.
-merge_request: 8137
-author:
diff --git a/changelogs/unreleased/no-sidebar-on-action-btn-click.yml b/changelogs/unreleased/no-sidebar-on-action-btn-click.yml
deleted file mode 100644
index 09e0b3a12d8..00000000000
--- a/changelogs/unreleased/no-sidebar-on-action-btn-click.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: dismiss sidebar on repo buttons click
-merge_request: 8798
-author: Adam Pahlevi
diff --git a/changelogs/unreleased/no_project_notes.yml b/changelogs/unreleased/no_project_notes.yml
deleted file mode 100644
index 6106c027360..00000000000
--- a/changelogs/unreleased/no_project_notes.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Support notes when a project is not specified (personal snippet notes)
-merge_request: 8468
-author:
diff --git a/changelogs/unreleased/only-yield-valid-reference-matches.yml b/changelogs/unreleased/only-yield-valid-reference-matches.yml
new file mode 100644
index 00000000000..95da3cc56fd
--- /dev/null
+++ b/changelogs/unreleased/only-yield-valid-reference-matches.yml
@@ -0,0 +1,4 @@
+---
+title: Only yield valid references in ReferenceFilter.references_in
+merge_request:
+author:
diff --git a/changelogs/unreleased/option-to-be-notified-of-own-activity.yml b/changelogs/unreleased/option-to-be-notified-of-own-activity.yml
new file mode 100644
index 00000000000..c2e0410cc33
--- /dev/null
+++ b/changelogs/unreleased/option-to-be-notified-of-own-activity.yml
@@ -0,0 +1,4 @@
+---
+title: Add option to receive email notifications about your own activity
+merge_request: 8836
+author: Richard Macklin
diff --git a/changelogs/unreleased/pages-0-3-2.yml b/changelogs/unreleased/pages-0-3-2.yml
new file mode 100644
index 00000000000..f660379f2e6
--- /dev/null
+++ b/changelogs/unreleased/pages-0-3-2.yml
@@ -0,0 +1,4 @@
+---
+title: Upgrade GitLab Pages to v0.3.2
+merge_request:
+author:
diff --git a/changelogs/unreleased/paginate-all-the-things.yml b/changelogs/unreleased/paginate-all-the-things.yml
new file mode 100644
index 00000000000..52f23ba52a9
--- /dev/null
+++ b/changelogs/unreleased/paginate-all-the-things.yml
@@ -0,0 +1,4 @@
+---
+title: 'API: Paginate all endpoints that return an array'
+merge_request: 8606
+author: Robert Schilling
diff --git a/changelogs/unreleased/pass in current_user in MergeRequest and MergeRequestsHelper.yml b/changelogs/unreleased/pass in current_user in MergeRequest and MergeRequestsHelper.yml
new file mode 100644
index 00000000000..0751047c3c0
--- /dev/null
+++ b/changelogs/unreleased/pass in current_user in MergeRequest and MergeRequestsHelper.yml
@@ -0,0 +1,4 @@
+---
+title: pass in current_user in MergeRequest and MergeRequestsHelper
+merge_request: 8624
+author: Dongqing Hu
diff --git a/changelogs/unreleased/pass_coverage_value_to_commit_status_api.yml b/changelogs/unreleased/pass_coverage_value_to_commit_status_api.yml
new file mode 100644
index 00000000000..74e0c18fa67
--- /dev/null
+++ b/changelogs/unreleased/pass_coverage_value_to_commit_status_api.yml
@@ -0,0 +1,4 @@
+---
+title: Make it possible to pass coverage value to commit status API
+merge_request: 9214
+author: wendy0402
diff --git a/changelogs/unreleased/pms-lowercase-system-notes.yml b/changelogs/unreleased/pms-lowercase-system-notes.yml
deleted file mode 100644
index c2fa1a7fad0..00000000000
--- a/changelogs/unreleased/pms-lowercase-system-notes.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Make all system notes lowercase
-merge_request: 8807
-author:
diff --git a/changelogs/unreleased/protected-branch-dropdown-titles.yml b/changelogs/unreleased/protected-branch-dropdown-titles.yml
new file mode 100644
index 00000000000..df82cc00fc9
--- /dev/null
+++ b/changelogs/unreleased/protected-branch-dropdown-titles.yml
@@ -0,0 +1,4 @@
+---
+title: Added headers to protected branch access dropdowns
+merge_request:
+author:
diff --git a/changelogs/unreleased/quick-submit-fixture.yml b/changelogs/unreleased/quick-submit-fixture.yml
new file mode 100644
index 00000000000..a2cf05dabec
--- /dev/null
+++ b/changelogs/unreleased/quick-submit-fixture.yml
@@ -0,0 +1,4 @@
+---
+title: Replace static fixture for behaviors/quick_submit_spec.js
+merge_request: 9086
+author: winniehell
diff --git a/changelogs/unreleased/redesign-searchbar-admin-project-26794.yml b/changelogs/unreleased/redesign-searchbar-admin-project-26794.yml
deleted file mode 100644
index 547a7c6755c..00000000000
--- a/changelogs/unreleased/redesign-searchbar-admin-project-26794.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Redesign searchbar in admin project list
-merge_request: 8776
-author:
diff --git a/changelogs/unreleased/redirect-to-commit-when-only-commit-found.yml b/changelogs/unreleased/redirect-to-commit-when-only-commit-found.yml
deleted file mode 100644
index e0f7e11b6d1..00000000000
--- a/changelogs/unreleased/redirect-to-commit-when-only-commit-found.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Search feature: redirects to commit page if query is commit sha and only commit
- found'
-merge_request: 8028
-author: YarNayar
diff --git a/changelogs/unreleased/refresh-permissions-when-moving-projects.yml b/changelogs/unreleased/refresh-permissions-when-moving-projects.yml
deleted file mode 100644
index a94bcdaa9a3..00000000000
--- a/changelogs/unreleased/refresh-permissions-when-moving-projects.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Refresh authorizations when transferring projects
-merge_request:
-author:
diff --git a/changelogs/unreleased/relative-url-assets.yml b/changelogs/unreleased/relative-url-assets.yml
deleted file mode 100644
index 0877664aca4..00000000000
--- a/changelogs/unreleased/relative-url-assets.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: allow relative url change without recompiling frontend assets
-merge_request: 8831
-author:
diff --git a/changelogs/unreleased/removal_of_unused_parameter.yml b/changelogs/unreleased/removal_of_unused_parameter.yml
new file mode 100644
index 00000000000..26bffafd9d9
--- /dev/null
+++ b/changelogs/unreleased/removal_of_unused_parameter.yml
@@ -0,0 +1,4 @@
+---
+title: 'removed unused parameter ''status_only: true'''
+merge_request:
+author:
diff --git a/changelogs/unreleased/remove-deploy-key-endpoint.yml b/changelogs/unreleased/remove-deploy-key-endpoint.yml
deleted file mode 100644
index 3ff69adb4d3..00000000000
--- a/changelogs/unreleased/remove-deploy-key-endpoint.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: 'API: Remove /projects/:id/keys/.. endpoints'
-merge_request: 8716
-author: Robert Schilling
diff --git a/changelogs/unreleased/remove-inactive-default-email-services.yml b/changelogs/unreleased/remove-inactive-default-email-services.yml
new file mode 100644
index 00000000000..c32c1390e4e
--- /dev/null
+++ b/changelogs/unreleased/remove-inactive-default-email-services.yml
@@ -0,0 +1,4 @@
+---
+title: Remove inactive default email services
+merge_request: 8987
+author:
diff --git a/changelogs/unreleased/remove-jquery-ui-datepicker.yml b/changelogs/unreleased/remove-jquery-ui-datepicker.yml
new file mode 100644
index 00000000000..cd00690d774
--- /dev/null
+++ b/changelogs/unreleased/remove-jquery-ui-datepicker.yml
@@ -0,0 +1,4 @@
+---
+title: Replaced jQuery UI datepicker
+merge_request:
+author:
diff --git a/changelogs/unreleased/remove-jquery-ui-sortable.yml b/changelogs/unreleased/remove-jquery-ui-sortable.yml
new file mode 100644
index 00000000000..35f47822738
--- /dev/null
+++ b/changelogs/unreleased/remove-jquery-ui-sortable.yml
@@ -0,0 +1,4 @@
+---
+title: Replaced jQuery UI sortable
+merge_request:
+author:
diff --git a/changelogs/unreleased/remove-sidekiq-backup-ar-threads.yml b/changelogs/unreleased/remove-sidekiq-backup-ar-threads.yml
deleted file mode 100644
index f42aa6fae79..00000000000
--- a/changelogs/unreleased/remove-sidekiq-backup-ar-threads.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Don't use backup Active Record connections for Sidekiq
-merge_request:
-author:
diff --git a/changelogs/unreleased/rename-retry-failed-pipeline-to-retry.yml b/changelogs/unreleased/rename-retry-failed-pipeline-to-retry.yml
new file mode 100644
index 00000000000..b813127b1e6
--- /dev/null
+++ b/changelogs/unreleased/rename-retry-failed-pipeline-to-retry.yml
@@ -0,0 +1,4 @@
+---
+title: Rename retry failed button on pipeline page to just retry
+merge_request:
+author:
diff --git a/changelogs/unreleased/rename_delete_services.yml b/changelogs/unreleased/rename_delete_services.yml
new file mode 100644
index 00000000000..686a1ef3d55
--- /dev/null
+++ b/changelogs/unreleased/rename_delete_services.yml
@@ -0,0 +1,4 @@
+---
+title: Fix inconsistent naming for services that delete things
+merge_request: 5803
+author: dixpac
diff --git a/changelogs/unreleased/rename_files_delete_service.yml b/changelogs/unreleased/rename_files_delete_service.yml
new file mode 100644
index 00000000000..4de1c5b0d63
--- /dev/null
+++ b/changelogs/unreleased/rename_files_delete_service.yml
@@ -0,0 +1,4 @@
+---
+title: Rename Files::DeleteService to Files::DestroyService
+merge_request: 9110
+author: dixpac
diff --git a/changelogs/unreleased/replace-npm-with-yarn.yml b/changelogs/unreleased/replace-npm-with-yarn.yml
new file mode 100644
index 00000000000..5e795eb0c8d
--- /dev/null
+++ b/changelogs/unreleased/replace-npm-with-yarn.yml
@@ -0,0 +1,4 @@
+---
+title: replace npm with yarn and add yarn.lock
+merge_request: 9055
+author:
diff --git a/changelogs/unreleased/requires-input-fixture.yml b/changelogs/unreleased/requires-input-fixture.yml
new file mode 100644
index 00000000000..be674499429
--- /dev/null
+++ b/changelogs/unreleased/requires-input-fixture.yml
@@ -0,0 +1,4 @@
+---
+title: Replace static fixture for behaviors/requires_input_spec.js
+merge_request: 9162
+author: winniehell
diff --git a/changelogs/unreleased/route-map.yml b/changelogs/unreleased/route-map.yml
deleted file mode 100644
index 9b6df0c54af..00000000000
--- a/changelogs/unreleased/route-map.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add 'View on [env]' link to blobs and individual files in diffs
-merge_request: 8867
-author:
diff --git a/changelogs/unreleased/seed-abuse-reports.yml b/changelogs/unreleased/seed-abuse-reports.yml
new file mode 100644
index 00000000000..6fbcb81ae3f
--- /dev/null
+++ b/changelogs/unreleased/seed-abuse-reports.yml
@@ -0,0 +1,4 @@
+---
+title: Seed abuse reports for development
+merge_request:
+author:
diff --git a/changelogs/unreleased/sh-delete-user-permission-check.yml b/changelogs/unreleased/sh-delete-user-permission-check.yml
new file mode 100644
index 00000000000..c0e79aae2a8
--- /dev/null
+++ b/changelogs/unreleased/sh-delete-user-permission-check.yml
@@ -0,0 +1,4 @@
+---
+title: Add user deletion permission check in `Users::DestroyService`
+merge_request:
+author:
diff --git a/changelogs/unreleased/slash-commands-typo.yml b/changelogs/unreleased/slash-commands-typo.yml
deleted file mode 100644
index e6ffb94bd08..00000000000
--- a/changelogs/unreleased/slash-commands-typo.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixed "substract" typo on /help/user/project/slash_commands
-merge_request: 8976
-author: Jason Aquino
diff --git a/changelogs/unreleased/small-screen-fullscreen-button.yml b/changelogs/unreleased/small-screen-fullscreen-button.yml
deleted file mode 100644
index f4c269bc473..00000000000
--- a/changelogs/unreleased/small-screen-fullscreen-button.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Display fullscreen button on small screens
-merge_request: 5302
-author: winniehell
diff --git a/changelogs/unreleased/snippets-search.yml b/changelogs/unreleased/snippets-search.yml
new file mode 100644
index 00000000000..00cf34f4a48
--- /dev/null
+++ b/changelogs/unreleased/snippets-search.yml
@@ -0,0 +1,4 @@
+---
+title: Fix snippets search result spacing
+merge_request:
+author:
diff --git a/changelogs/unreleased/static-navbar.yml b/changelogs/unreleased/static-navbar.yml
new file mode 100644
index 00000000000..eaf478a48d0
--- /dev/null
+++ b/changelogs/unreleased/static-navbar.yml
@@ -0,0 +1,4 @@
+---
+title: Remove fixed positioning from top nav
+merge_request: !7547
+author:
diff --git a/changelogs/unreleased/task_list_refactor.yml b/changelogs/unreleased/task_list_refactor.yml
new file mode 100644
index 00000000000..68942dadaa8
--- /dev/null
+++ b/changelogs/unreleased/task_list_refactor.yml
@@ -0,0 +1,4 @@
+---
+title: Deduplicate markdown task lists
+merge_request:
+author:
diff --git a/changelogs/unreleased/tc-only-mr-button-if-allowed.yml b/changelogs/unreleased/tc-only-mr-button-if-allowed.yml
deleted file mode 100644
index a7f5dcb560c..00000000000
--- a/changelogs/unreleased/tc-only-mr-button-if-allowed.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Only show Merge Request button when user can create a MR
-merge_request: 8639
-author:
diff --git a/changelogs/unreleased/terminal-max-session-time.yml b/changelogs/unreleased/terminal-max-session-time.yml
deleted file mode 100644
index db1e66770d1..00000000000
--- a/changelogs/unreleased/terminal-max-session-time.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Introduce maximum session time for terminal websocket connection
-merge_request: 8413
-author:
diff --git a/changelogs/unreleased/upgrade-omniauth.yml b/changelogs/unreleased/upgrade-omniauth.yml
deleted file mode 100644
index 7e0334566dc..00000000000
--- a/changelogs/unreleased/upgrade-omniauth.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Upgrade omniauth gem to 1.3.2
-merge_request:
-author:
diff --git a/changelogs/unreleased/wip-mr-from-commits.yml b/changelogs/unreleased/wip-mr-from-commits.yml
deleted file mode 100644
index 0083798be08..00000000000
--- a/changelogs/unreleased/wip-mr-from-commits.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Mark MR as WIP when pushing WIP commits
-merge_request: 8124
-author: Jurre Stender @jurre
diff --git a/changelogs/unreleased/zj-fix-slash-command-labels.yml b/changelogs/unreleased/zj-fix-slash-command-labels.yml
new file mode 100644
index 00000000000..93b7194dd4e
--- /dev/null
+++ b/changelogs/unreleased/zj-fix-slash-command-labels.yml
@@ -0,0 +1,4 @@
+---
+title: Chat slash commands show labels correctly
+merge_request:
+author:
diff --git a/changelogs/unreleased/zj-format-chat-messages.yml b/changelogs/unreleased/zj-format-chat-messages.yml
deleted file mode 100644
index 2494884f5c9..00000000000
--- a/changelogs/unreleased/zj-format-chat-messages.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Reformat messages ChatOps
-merge_request: 8528
-author:
diff --git a/changelogs/unreleased/zj-requeue-pending-delete.yml b/changelogs/unreleased/zj-requeue-pending-delete.yml
deleted file mode 100644
index 464c5948f8c..00000000000
--- a/changelogs/unreleased/zj-requeue-pending-delete.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Requeue pending deletion projects
-merge_request:
-author:
diff --git a/config/dependency_decisions.yml b/config/dependency_decisions.yml
index aabe859730a..072ed8a3864 100644
--- a/config/dependency_decisions.yml
+++ b/config/dependency_decisions.yml
@@ -302,3 +302,27 @@
:why: https://github.com/dchest/tweetnacl-js/blob/master/LICENSE
:versions: []
:when: 2017-01-14 20:10:57.812077000 Z
+- - :approve
+ - wordwrap
+ - :who: Mike Greiling
+ :why: https://github.com/substack/node-wordwrap/blob/0.0.3/LICENSE
+ :versions: []
+ :when: 2017-02-08 20:17:13.084968000 Z
+- - :approve
+ - spdx-expression-parse
+ - :who: Mike Greiling
+ :why: https://github.com/kemitchell/spdx-expression-parse.js/blob/v1.0.4/LICENSE
+ :versions: []
+ :when: 2017-02-08 22:33:01.806977000 Z
+- - :approve
+ - spdx-license-ids
+ - :who: Mike Greiling
+ :why: https://github.com/shinnn/spdx-license-ids/blob/v1.2.2/LICENSE
+ :versions: []
+ :when: 2017-02-08 22:35:00.225232000 Z
+- - :approve
+ - opener
+ - :who: Mike Greiling
+ :why: https://github.com/domenic/opener/blob/1.4.3/LICENSE.txt
+ :versions: []
+ :when: 2017-02-21 22:33:41.729629000 Z
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index cc1af77a1de..a82ff605a70 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -76,14 +76,6 @@ production: &base
# default_can_create_group: false # default: true
# username_changing_enabled: false # default: true - User can change her username/namespace
- ## Default theme ID
- ## 1 - Graphite
- ## 2 - Charcoal
- ## 3 - Green
- ## 4 - Gray
- ## 5 - Violet
- ## 6 - Blue
- # default_theme: 2 # default: 2
## Automatic issue closing
# If a commit message matches this regular expression, all issues referenced from the matched text will be closed.
@@ -611,4 +603,4 @@ test:
admin_group: ''
staging:
- <<: *base \ No newline at end of file
+ <<: *base
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index ab59394cb0c..c64ae15fa92 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -14,12 +14,15 @@ class Settings < Settingslogic
end
def build_gitlab_ci_url
- if on_standard_port?(gitlab)
- custom_port = nil
- else
- custom_port = ":#{gitlab.port}"
- end
- [ gitlab.protocol,
+ custom_port =
+ if on_standard_port?(gitlab)
+ nil
+ else
+ ":#{gitlab.port}"
+ end
+
+ [
+ gitlab.protocol,
"://",
gitlab.host,
custom_port,
@@ -80,7 +83,9 @@ class Settings < Settingslogic
def base_url(config)
custom_port = on_standard_port?(config) ? nil : ":#{config.port}"
- [ config.protocol,
+
+ [
+ config.protocol,
"://",
config.host,
custom_port
@@ -160,15 +165,16 @@ if github_settings
github_settings["args"] ||= Settingslogic.new({})
- if github_settings["url"].include?(github_default_url)
- github_settings["args"]["client_options"] = OmniAuth::Strategies::GitHub.default_options[:client_options]
- else
- github_settings["args"]["client_options"] = {
- "site" => File.join(github_settings["url"], "api/v3"),
- "authorize_url" => File.join(github_settings["url"], "login/oauth/authorize"),
- "token_url" => File.join(github_settings["url"], "login/oauth/access_token")
- }
- end
+ github_settings["args"]["client_options"] =
+ if github_settings["url"].include?(github_default_url)
+ OmniAuth::Strategies::GitHub.default_options[:client_options]
+ else
+ {
+ "site" => File.join(github_settings["url"], "api/v3"),
+ "authorize_url" => File.join(github_settings["url"], "login/oauth/authorize"),
+ "token_url" => File.join(github_settings["url"], "login/oauth/access_token")
+ }
+ end
end
Settings['shared'] ||= Settingslogic.new({})
@@ -183,7 +189,6 @@ Settings['gitlab'] ||= Settingslogic.new({})
Settings.gitlab['default_projects_limit'] ||= 10
Settings.gitlab['default_branch_protection'] ||= 2
Settings.gitlab['default_can_create_group'] = true if Settings.gitlab['default_can_create_group'].nil?
-Settings.gitlab['default_theme'] = Gitlab::Themes::APPLICATION_DEFAULT if Settings.gitlab['default_theme'].nil?
Settings.gitlab['host'] ||= ENV['GITLAB_HOST'] || 'localhost'
Settings.gitlab['ssh_host'] ||= Settings.gitlab.host
Settings.gitlab['https'] = false if Settings.gitlab['https'].nil?
diff --git a/config/initializers/4_ci_app.rb b/config/initializers/4_ci_app.rb
deleted file mode 100644
index d252e403102..00000000000
--- a/config/initializers/4_ci_app.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-module GitlabCi
- VERSION = Gitlab::VERSION
- REVISION = Gitlab::REVISION
-
- def self.config
- Settings
- end
-end
diff --git a/config/initializers/acts_as_taggable.rb b/config/initializers/acts_as_taggable.rb
new file mode 100644
index 00000000000..c564c0cab11
--- /dev/null
+++ b/config/initializers/acts_as_taggable.rb
@@ -0,0 +1,5 @@
+ActsAsTaggableOn.strict_case_match = true
+
+# tags_counter enables caching count of tags which results in an update whenever a tag is added or removed
+# since the count is not used anywhere its better performance wise to disable this cache
+ActsAsTaggableOn.tags_counter = false
diff --git a/config/initializers/additional_headers_interceptor.rb b/config/initializers/additional_headers_interceptor.rb
new file mode 100644
index 00000000000..b9159e7c06c
--- /dev/null
+++ b/config/initializers/additional_headers_interceptor.rb
@@ -0,0 +1 @@
+ActionMailer::Base.register_interceptor(AdditionalEmailHeadersInterceptor)
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index a8afc36fc78..3b1317030bc 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -24,7 +24,7 @@ Devise.setup do |config|
# session. If you need permissions, you should implement that in a before filter.
# You can also supply a hash where the value is a boolean determining whether
# or not authentication should be aborted when the value is not present.
- config.authentication_keys = [ :login ]
+ config.authentication_keys = [:login]
# Configure parameters from the request object used for authentication. Each entry
# given should be a request method and it will automatically be passed to the
@@ -36,12 +36,12 @@ Devise.setup do |config|
# Configure which authentication keys should be case-insensitive.
# These keys will be downcased upon creating or modifying a user and when used
# to authenticate or find a user. Default is :email.
- config.case_insensitive_keys = [ :email ]
+ config.case_insensitive_keys = [:email]
# Configure which authentication keys should have whitespace stripped.
# These keys will have whitespace before and after removed upon creating or
# modifying a user and when used to authenticate or find a user. Default is :email.
- config.strip_whitespace_keys = [ :email ]
+ config.strip_whitespace_keys = [:email]
# Tell if authentication through request.params is enabled. True by default.
# config.params_authenticatable = true
@@ -124,7 +124,7 @@ Devise.setup do |config|
config.lock_strategy = :failed_attempts
# Defines which key will be used when locking and unlocking an account
- config.unlock_keys = [ :email ]
+ config.unlock_keys = [:email]
# Defines which strategy will be used to unlock an account.
# :email = Sends an unlock link to the user email
@@ -240,6 +240,17 @@ Devise.setup do |config|
true
end
end
+ if provider['name'] == 'authentiq'
+ provider['args'][:remote_sign_out_handler] = lambda do |request|
+ authentiq_session = request.params['sid']
+ if Gitlab::OAuth::Session.valid?(:authentiq, authentiq_session)
+ Gitlab::OAuth::Session.destroy(:authentiq, authentiq_session)
+ true
+ else
+ false
+ end
+ end
+ end
if provider['name'] == 'shibboleth'
provider['args'][:fail_with_empty_uid] = true
diff --git a/config/initializers/gollum.rb b/config/initializers/gollum.rb
index 703f24f93b2..1ebe3c7a742 100644
--- a/config/initializers/gollum.rb
+++ b/config/initializers/gollum.rb
@@ -1,5 +1,5 @@
module Gollum
- GIT_ADAPTER = "rugged"
+ GIT_ADAPTER = "rugged".freeze
end
require "gollum-lib"
diff --git a/config/initializers/health_check.rb b/config/initializers/health_check.rb
index 4c91a61fb4a..959daa93f78 100644
--- a/config/initializers/health_check.rb
+++ b/config/initializers/health_check.rb
@@ -1,4 +1,4 @@
HealthCheck.setup do |config|
- config.standard_checks = ['database', 'migrations', 'cache']
- config.full_checks = ['database', 'migrations', 'cache']
+ config.standard_checks = %w(database migrations cache)
+ config.full_checks = %w(database migrations cache)
end
diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb
index e0702e06cc9..a1517e6afc8 100644
--- a/config/initializers/metrics.rb
+++ b/config/initializers/metrics.rb
@@ -20,13 +20,13 @@ def instrument_classes(instrumentation)
# Path to search => prefix to strip from constant
paths_to_instrument = {
- ['app', 'finders'] => ['app', 'finders'],
- ['app', 'mailers', 'emails'] => ['app', 'mailers'],
- ['app', 'services', '**'] => ['app', 'services'],
- ['lib', 'gitlab', 'conflicts'] => ['lib'],
- ['lib', 'gitlab', 'diff'] => ['lib'],
- ['lib', 'gitlab', 'email', 'message'] => ['lib'],
- ['lib', 'gitlab', 'checks'] => ['lib']
+ %w(app finders) => %w(app finders),
+ %w(app mailers emails) => %w(app mailers),
+ %w(app services **) => %w(app services),
+ %w(lib gitlab conflicts) => ['lib'],
+ %w(lib gitlab diff) => ['lib'],
+ %w(lib gitlab email message) => ['lib'],
+ %w(lib gitlab checks) => ['lib']
}
paths_to_instrument.each do |(path, prefix)|
diff --git a/config/initializers/mysql_ignore_postgresql_options.rb b/config/initializers/mysql_ignore_postgresql_options.rb
index 835f3ec5574..9a569be7674 100644
--- a/config/initializers/mysql_ignore_postgresql_options.rb
+++ b/config/initializers/mysql_ignore_postgresql_options.rb
@@ -31,7 +31,7 @@ if defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter)
end
def add_index_options(table_name, column_name, options = {})
- if options[:using] and options[:using] == :gin
+ if options[:using] && options[:using] == :gin
options = options.dup
options.delete(:using)
end
diff --git a/config/initializers/rack_lineprof.rb b/config/initializers/rack_lineprof.rb
index 22e77a32c61..f7172fce9bc 100644
--- a/config/initializers/rack_lineprof.rb
+++ b/config/initializers/rack_lineprof.rb
@@ -1,7 +1,7 @@
# The default colors of rack-lineprof can be very hard to look at in terminals
# with darker backgrounds. This patch tweaks the colors a bit so the output is
# actually readable.
-if Rails.env.development? and RUBY_ENGINE == 'ruby' and ENV['ENABLE_LINEPROF']
+if Rails.env.development? && RUBY_ENGINE == 'ruby' && ENV['ENABLE_LINEPROF']
Rails.application.config.middleware.use(Rack::Lineprof)
module Rack
diff --git a/config/initializers/rspec_profiling.rb b/config/initializers/rspec_profiling.rb
index f462e654b2c..0ef9f51e5cf 100644
--- a/config/initializers/rspec_profiling.rb
+++ b/config/initializers/rspec_profiling.rb
@@ -4,6 +4,12 @@ module RspecProfilingConnection
end
end
+module RspecProfilingGitBranchCi
+ def branch
+ ENV['CI_BUILD_REF_NAME'] || super
+ end
+end
+
if Rails.env.test?
RspecProfiling.configure do |config|
if ENV['RSPEC_PROFILING_POSTGRES_URL']
@@ -11,4 +17,6 @@ if Rails.env.test?
config.collector = RspecProfiling::Collectors::PSQL
end
end
+
+ RspecProfiling::VCS::Git.prepend(RspecProfilingGitBranchCi) if ENV.has_key?('CI')
end
diff --git a/config/initializers/trusted_proxies.rb b/config/initializers/trusted_proxies.rb
index cd869657c53..fc4f02453d7 100644
--- a/config/initializers/trusted_proxies.rb
+++ b/config/initializers/trusted_proxies.rb
@@ -21,4 +21,4 @@ gitlab_trusted_proxies = Array(Gitlab.config.gitlab.trusted_proxies).map do |pro
end.compact
Rails.application.config.action_dispatch.trusted_proxies = (
- [ '127.0.0.1', '::1' ] + gitlab_trusted_proxies)
+ ['127.0.0.1', '::1'] + gitlab_trusted_proxies)
diff --git a/config/initializers/workhorse_multipart.rb b/config/initializers/workhorse_multipart.rb
index 84d809741c4..064e5964f09 100644
--- a/config/initializers/workhorse_multipart.rb
+++ b/config/initializers/workhorse_multipart.rb
@@ -10,7 +10,7 @@ end
#
module Gitlab
module StrongParameterScalars
- GITLAB_PERMITTED_SCALAR_TYPES = [::UploadedFile]
+ GITLAB_PERMITTED_SCALAR_TYPES = [::UploadedFile].freeze
def permitted_scalar?(value)
super || GITLAB_PERMITTED_SCALAR_TYPES.any? { |type| value.is_a?(type) }
diff --git a/config/karma.config.js b/config/karma.config.js
index 44229e2ee88..2f3cc932413 100644
--- a/config/karma.config.js
+++ b/config/karma.config.js
@@ -2,8 +2,20 @@ var path = require('path');
var webpackConfig = require('./webpack.config.js');
var ROOT_PATH = path.resolve(__dirname, '..');
+// add coverage instrumentation to babel config
+if (webpackConfig && webpackConfig.module && webpackConfig.module.rules) {
+ var babelConfig = webpackConfig.module.rules.find(function (rule) {
+ return rule.loader === 'babel-loader';
+ });
+
+ babelConfig.options = babelConfig.options || {};
+ babelConfig.options.plugins = babelConfig.options.plugins || [];
+ babelConfig.options.plugins.push('istanbul');
+}
+
// Karma configuration
module.exports = function(config) {
+ var progressReporter = process.env.CI ? 'mocha' : 'progress';
config.set({
basePath: ROOT_PATH,
browsers: ['PhantomJS'],
@@ -15,6 +27,13 @@ module.exports = function(config) {
preprocessors: {
'spec/javascripts/**/*.js?(.es6)': ['webpack', 'sourcemap'],
},
+ reporters: [progressReporter, 'coverage-istanbul'],
+ coverageIstanbulReporter: {
+ reports: ['html', 'text-summary'],
+ dir: 'coverage-javascript/',
+ subdir: '.',
+ fixWebpackSourcePaths: true
+ },
webpack: webpackConfig,
webpackMiddleware: { stats: 'errors-only' },
});
diff --git a/config/mail_room.yml b/config/mail_room.yml
index 774c5350a45..88d93d4bc6b 100644
--- a/config/mail_room.yml
+++ b/config/mail_room.yml
@@ -1,9 +1,6 @@
-# If you change this file in a Merge Request, please also create
-# a Merge Request on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests
-#
:mailboxes:
<%
- require_relative "lib/gitlab/mail_room" unless defined?(Gitlab::MailRoom)
+ require_relative "../lib/gitlab/mail_room" unless defined?(Gitlab::MailRoom)
config = Gitlab::MailRoom.config
if Gitlab::MailRoom.enabled?
diff --git a/config/routes/dashboard.rb b/config/routes/dashboard.rb
index fb20c63bc63..adc3ad207cc 100644
--- a/config/routes/dashboard.rb
+++ b/config/routes/dashboard.rb
@@ -14,6 +14,9 @@ resource :dashboard, controller: 'dashboard', only: [] do
collection do
delete :destroy_all
end
+ member do
+ patch :restore
+ end
end
resources :projects, only: [:index] do
diff --git a/config/routes/group.rb b/config/routes/group.rb
index 60a1175fe80..73f69d76995 100644
--- a/config/routes/group.rb
+++ b/config/routes/group.rb
@@ -25,5 +25,6 @@ scope(path: 'groups/*id',
get :merge_requests, as: :merge_requests_group
get :projects, as: :projects_group
get :activity, as: :activity_group
+ get :subgroups, as: :subgroups_group
get '/', action: :show, as: :group_canonical
end
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 2ac98cf3842..84f123ff717 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -156,6 +156,10 @@ constraints(ProjectUrlConstrainer.new) do
get :terminal
get '/terminal.ws/authorize', to: 'environments#terminal_websocket_authorize', constraints: { format: nil }
end
+
+ collection do
+ get :folder, path: 'folders/:id'
+ end
end
resource :cycle_analytics, only: [:show]
diff --git a/config/routes/sidekiq.rb b/config/routes/sidekiq.rb
index d3e6bc4c292..0fa23f2b3d0 100644
--- a/config/routes/sidekiq.rb
+++ b/config/routes/sidekiq.rb
@@ -1,4 +1,4 @@
-constraint = lambda { |request| request.env['warden'].authenticate? and request.env['warden'].user.admin? }
+constraint = lambda { |request| request.env['warden'].authenticate? && request.env['warden'].user.admin? }
constraints constraint do
mount Sidekiq::Web, at: '/admin/sidekiq', as: :sidekiq
end
diff --git a/config/routes/wiki.rb b/config/routes/wiki.rb
index dad746d59a1..a6b3f5d4693 100644
--- a/config/routes/wiki.rb
+++ b/config/routes/wiki.rb
@@ -1,4 +1,4 @@
-WIKI_SLUG_ID = { id: /\S+/ } unless defined? WIKI_SLUG_ID
+WIKI_SLUG_ID = { id: /\S+/ }.freeze unless defined? WIKI_SLUG_ID
scope(controller: :wikis) do
scope(path: 'wikis', as: :wikis) do
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 56bf4e6b1de..97620cc9c7f 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -21,7 +21,7 @@
- [post_receive, 5]
- [merge, 5]
- [update_merge_requests, 3]
- - [process_commit, 2]
+ - [process_commit, 3]
- [new_note, 2]
- [build, 2]
- [pipeline, 2]
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 953ae463c86..e754f68553a 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -5,11 +5,14 @@ var path = require('path');
var webpack = require('webpack');
var StatsPlugin = require('stats-webpack-plugin');
var CompressionPlugin = require('compression-webpack-plugin');
+var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
var ROOT_PATH = path.resolve(__dirname, '..');
var IS_PRODUCTION = process.env.NODE_ENV === 'production';
var IS_DEV_SERVER = process.argv[1].indexOf('webpack-dev-server') !== -1;
var DEV_SERVER_PORT = parseInt(process.env.DEV_SERVER_PORT, 10) || 3808;
+var DEV_SERVER_LIVERELOAD = process.env.DEV_SERVER_LIVERELOAD !== 'false';
+var WEBPACK_REPORT = process.env.WEBPACK_REPORT;
var config = {
context: path.join(ROOT_PATH, 'app/assets/javascripts'),
@@ -17,11 +20,12 @@ var config = {
application: './application.js',
blob_edit: './blob_edit/blob_edit_bundle.js',
boards: './boards/boards_bundle.js',
- boards_test: './boards/test_utils/simulate_drag.js',
+ simulate_drag: './test_utils/simulate_drag.js',
cycle_analytics: './cycle_analytics/cycle_analytics_bundle.js',
commit_pipelines: './commit/pipelines/pipelines_bundle.js',
diff_notes: './diff_notes/diff_notes_bundle.js',
environments: './environments/environments_bundle.js',
+ environments_folder: './environments/folder/environments_folder_bundle.js',
filtered_search: './filtered_search/filtered_search_bundle.js',
graphs: './graphs/graphs_bundle.js',
issuable: './issuable/issuable_bundle.js',
@@ -48,26 +52,17 @@ var config = {
devtool: 'inline-source-map',
module: {
- loaders: [
+ rules: [
{
test: /\.(js|es6)$/,
exclude: /(node_modules|vendor\/assets)/,
loader: 'babel-loader',
- query: {
- // 'use strict' was broken in sprockets-es6 due to sprockets concatination method.
- // many es5 strict errors which were never caught ended up in our es6 assets as a result.
- // this hack is necessary until they can be fixed.
- blacklist: ['useStrict']
+ options: {
+ presets: [
+ ["es2015", {"modules": false}],
+ 'stage-2'
+ ]
}
- },
- {
- test: /\.(js|es6)$/,
- loader: 'imports-loader',
- query: 'this=>window'
- },
- {
- test: /\.json$/,
- loader: 'json-loader'
}
]
},
@@ -82,20 +77,17 @@ var config = {
modules: false,
assets: true
}),
- new CompressionPlugin({
- asset: '[path].gz[query]',
- }),
+ new webpack.IgnorePlugin(/moment/, /pikaday/),
],
resolve: {
- extensions: ['', '.js', '.es6', '.js.es6'],
+ extensions: ['.js', '.es6', '.js.es6'],
alias: {
'~': path.join(ROOT_PATH, 'app/assets/javascripts'),
'bootstrap/js': 'bootstrap-sass/assets/javascripts/bootstrap',
'emoji-aliases$': path.join(ROOT_PATH, 'fixtures/emojis/aliases.json'),
'vendor': path.join(ROOT_PATH, 'vendor/assets/javascripts'),
- 'vue$': 'vue/dist/vue.js',
- 'vue-resource$': 'vue-resource/dist/vue-resource.js'
+ 'vue$': IS_PRODUCTION ? 'vue/dist/vue.min.js' : 'vue/dist/vue.js',
}
}
}
@@ -103,24 +95,43 @@ var config = {
if (IS_PRODUCTION) {
config.devtool = 'source-map';
config.plugins.push(
- new webpack.NoErrorsPlugin(),
+ new webpack.NoEmitOnErrorsPlugin(),
+ new webpack.LoaderOptionsPlugin({
+ minimize: true,
+ debug: false
+ }),
new webpack.optimize.UglifyJsPlugin({
- compress: { warnings: false }
+ sourceMap: true
}),
new webpack.DefinePlugin({
'process.env': { NODE_ENV: JSON.stringify('production') }
}),
- new webpack.optimize.DedupePlugin(),
- new webpack.optimize.OccurrenceOrderPlugin()
+ new CompressionPlugin({
+ asset: '[path].gz[query]',
+ })
);
}
if (IS_DEV_SERVER) {
config.devServer = {
port: DEV_SERVER_PORT,
- headers: { 'Access-Control-Allow-Origin': '*' }
+ headers: { 'Access-Control-Allow-Origin': '*' },
+ stats: 'errors-only',
+ inline: DEV_SERVER_LIVERELOAD
};
config.output.publicPath = '//localhost:' + DEV_SERVER_PORT + config.output.publicPath;
}
+if (WEBPACK_REPORT) {
+ config.plugins.push(
+ new BundleAnalyzerPlugin({
+ analyzerMode: 'static',
+ generateStatsFile: true,
+ openAnalyzer: false,
+ reportFilename: path.join(ROOT_PATH, 'webpack-report/index.html'),
+ statsFilename: path.join(ROOT_PATH, 'webpack-report/stats.json'),
+ })
+ );
+}
+
module.exports = config;
diff --git a/db/fixtures/development/18_abuse_reports.rb b/db/fixtures/development/18_abuse_reports.rb
new file mode 100644
index 00000000000..8618d10387a
--- /dev/null
+++ b/db/fixtures/development/18_abuse_reports.rb
@@ -0,0 +1,5 @@
+require 'factory_girl_rails'
+
+(AbuseReport.default_per_page + 3).times do
+ FactoryGirl.create(:abuse_report)
+end
diff --git a/db/migrate/20160519203051_add_developers_can_merge_to_protected_branches.rb b/db/migrate/20160519203051_add_developers_can_merge_to_protected_branches.rb
index 15ad8e8bcbb..ac50035eba4 100644
--- a/db/migrate/20160519203051_add_developers_can_merge_to_protected_branches.rb
+++ b/db/migrate/20160519203051_add_developers_can_merge_to_protected_branches.rb
@@ -1,9 +1,15 @@
class AddDevelopersCanMergeToProtectedBranches < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
+ DOWNTIME = false
+
disable_ddl_transaction!
- def change
+ def up
add_column_with_default :protected_branches, :developers_can_merge, :boolean, default: false, allow_null: false
end
+
+ def down
+ remove_column :protected_branches, :developers_can_merge
+ end
end
diff --git a/db/migrate/20160610201627_migrate_users_notification_level.rb b/db/migrate/20160610201627_migrate_users_notification_level.rb
index 760b766828e..ce4f00e25fa 100644
--- a/db/migrate/20160610201627_migrate_users_notification_level.rb
+++ b/db/migrate/20160610201627_migrate_users_notification_level.rb
@@ -2,6 +2,8 @@ class MigrateUsersNotificationLevel < ActiveRecord::Migration
# Migrates only users who changed their default notification level :participating
# creating a new record on notification settings table
+ DOWNTIME = false
+
def up
execute(%Q{
INSERT INTO notification_settings
diff --git a/db/migrate/20160801163709_add_submitted_as_ham_to_spam_logs.rb b/db/migrate/20160801163709_add_submitted_as_ham_to_spam_logs.rb
index 296f1dfac7b..20a77000ba8 100644
--- a/db/migrate/20160801163709_add_submitted_as_ham_to_spam_logs.rb
+++ b/db/migrate/20160801163709_add_submitted_as_ham_to_spam_logs.rb
@@ -14,7 +14,11 @@ class AddSubmittedAsHamToSpamLogs < ActiveRecord::Migration
disable_ddl_transaction!
- def change
+ def up
add_column_with_default :spam_logs, :submitted_as_ham, :boolean, default: false
end
+
+ def down
+ remove_column :spam_logs, :submitted_as_ham
+ end
end
diff --git a/db/migrate/20160829114652_add_markdown_cache_columns.rb b/db/migrate/20160829114652_add_markdown_cache_columns.rb
index 8753e55e058..9cb44dfa9f9 100644
--- a/db/migrate/20160829114652_add_markdown_cache_columns.rb
+++ b/db/migrate/20160829114652_add_markdown_cache_columns.rb
@@ -26,7 +26,7 @@ class AddMarkdownCacheColumns < ActiveRecord::Migration
projects: [:description],
releases: [:description],
snippets: [:title, :content],
- }
+ }.freeze
def change
COLUMNS.each do |table, columns|
diff --git a/db/migrate/20160831214543_migrate_project_features.rb b/db/migrate/20160831214543_migrate_project_features.rb
index 93f9821bc76..79a5fb29d64 100644
--- a/db/migrate/20160831214543_migrate_project_features.rb
+++ b/db/migrate/20160831214543_migrate_project_features.rb
@@ -3,7 +3,7 @@ class MigrateProjectFeatures < ActiveRecord::Migration
DOWNTIME = true
DOWNTIME_REASON =
- <<-EOT
+ <<-EOT.freeze
Migrating issues_enabled, merge_requests_enabled, wiki_enabled, builds_enabled, snippets_enabled fields from projects to
a new table called project_features.
EOT
diff --git a/db/migrate/20160919145149_add_group_id_to_labels.rb b/db/migrate/20160919145149_add_group_id_to_labels.rb
index 05e21af0584..d10f3a6d104 100644
--- a/db/migrate/20160919145149_add_group_id_to_labels.rb
+++ b/db/migrate/20160919145149_add_group_id_to_labels.rb
@@ -7,7 +7,7 @@ class AddGroupIdToLabels < ActiveRecord::Migration
def change
add_column :labels, :group_id, :integer
- add_foreign_key :labels, :namespaces, column: :group_id, on_delete: :cascade
+ add_foreign_key :labels, :namespaces, column: :group_id, on_delete: :cascade # rubocop: disable Migration/AddConcurrentForeignKey
add_concurrent_index :labels, :group_id
end
end
diff --git a/db/migrate/20161019190736_migrate_sidekiq_queues_from_default.rb b/db/migrate/20161019190736_migrate_sidekiq_queues_from_default.rb
index e875213ab96..9f502a8df73 100644
--- a/db/migrate/20161019190736_migrate_sidekiq_queues_from_default.rb
+++ b/db/migrate/20161019190736_migrate_sidekiq_queues_from_default.rb
@@ -71,7 +71,7 @@ class MigrateSidekiqQueuesFromDefault < ActiveRecord::Migration
'StuckCiBuildsWorker' => :cronjob,
'UpdateMergeRequestsWorker' => :update_merge_requests
}
- }
+ }.freeze
def up
Sidekiq.redis do |redis|
@@ -93,7 +93,7 @@ class MigrateSidekiqQueuesFromDefault < ActiveRecord::Migration
def migrate_from_queue(redis, queue, job_mapping)
while job = redis.lpop("queue:#{queue}")
- payload = JSON.load(job)
+ payload = JSON.parse(job)
new_queue = job_mapping[payload['class']]
# If we have no target queue to migrate to we're probably dealing with
diff --git a/db/migrate/20161020083353_add_pipeline_id_to_merge_request_metrics.rb b/db/migrate/20161020083353_add_pipeline_id_to_merge_request_metrics.rb
index f49df6802a7..2abfe47b776 100644
--- a/db/migrate/20161020083353_add_pipeline_id_to_merge_request_metrics.rb
+++ b/db/migrate/20161020083353_add_pipeline_id_to_merge_request_metrics.rb
@@ -28,6 +28,6 @@ class AddPipelineIdToMergeRequestMetrics < ActiveRecord::Migration
def change
add_column :merge_request_metrics, :pipeline_id, :integer
add_concurrent_index :merge_request_metrics, :pipeline_id
- add_foreign_key :merge_request_metrics, :ci_commits, column: :pipeline_id, on_delete: :cascade
+ add_foreign_key :merge_request_metrics, :ci_commits, column: :pipeline_id, on_delete: :cascade # rubocop: disable Migration/AddConcurrentForeignKey
end
end
diff --git a/db/migrate/20161024042317_migrate_mailroom_queue_from_default.rb b/db/migrate/20161024042317_migrate_mailroom_queue_from_default.rb
index 06d07bdb835..fc2e4c12b30 100644
--- a/db/migrate/20161024042317_migrate_mailroom_queue_from_default.rb
+++ b/db/migrate/20161024042317_migrate_mailroom_queue_from_default.rb
@@ -25,7 +25,7 @@ class MigrateMailroomQueueFromDefault < ActiveRecord::Migration
incoming_email: {
'EmailReceiverWorker' => :email_receiver
}
- }
+ }.freeze
def up
Sidekiq.redis do |redis|
@@ -47,7 +47,7 @@ class MigrateMailroomQueueFromDefault < ActiveRecord::Migration
def migrate_from_queue(redis, queue, job_mapping)
while job = redis.lpop("queue:#{queue}")
- payload = JSON.load(job)
+ payload = JSON.parse(job)
new_queue = job_mapping[payload['class']]
# If we have no target queue to migrate to we're probably dealing with
diff --git a/db/migrate/20161031171301_add_project_id_to_subscriptions.rb b/db/migrate/20161031171301_add_project_id_to_subscriptions.rb
index 97534679b59..d5c343dc527 100644
--- a/db/migrate/20161031171301_add_project_id_to_subscriptions.rb
+++ b/db/migrate/20161031171301_add_project_id_to_subscriptions.rb
@@ -5,7 +5,7 @@ class AddProjectIdToSubscriptions < ActiveRecord::Migration
def up
add_column :subscriptions, :project_id, :integer
- add_foreign_key :subscriptions, :projects, column: :project_id, on_delete: :cascade
+ add_foreign_key :subscriptions, :projects, column: :project_id, on_delete: :cascade # rubocop: disable Migration/AddConcurrentForeignKey
end
def down
diff --git a/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb b/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb
index 77e0c40d850..3e1f6b1627d 100644
--- a/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb
+++ b/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb
@@ -34,7 +34,7 @@ class MigrateProcessCommitWorkerJobs < ActiveRecord::Migration
new_jobs = []
while job = redis.lpop('queue:process_commit')
- payload = JSON.load(job)
+ payload = JSON.parse(job)
project = Project.find_including_path(payload['args'][0])
next unless project
@@ -75,7 +75,7 @@ class MigrateProcessCommitWorkerJobs < ActiveRecord::Migration
new_jobs = []
while job = redis.lpop('queue:process_commit')
- payload = JSON.load(job)
+ payload = JSON.parse(job)
payload['args'][2] = payload['args'][2]['id']
diff --git a/db/migrate/20161128142110_remove_unnecessary_indexes.rb b/db/migrate/20161128142110_remove_unnecessary_indexes.rb
index 9deab19782e..8100287ef48 100644
--- a/db/migrate/20161128142110_remove_unnecessary_indexes.rb
+++ b/db/migrate/20161128142110_remove_unnecessary_indexes.rb
@@ -12,7 +12,7 @@ class RemoveUnnecessaryIndexes < ActiveRecord::Migration
remove_index :award_emoji, column: :user_id if index_exists?(:award_emoji, :user_id)
remove_index :ci_builds, column: :commit_id if index_exists?(:ci_builds, :commit_id)
remove_index :deployments, column: :project_id if index_exists?(:deployments, :project_id)
- remove_index :deployments, column: ["project_id", "environment_id"] if index_exists?(:deployments, ["project_id", "environment_id"])
+ remove_index :deployments, column: %w(project_id environment_id) if index_exists?(:deployments, %w(project_id environment_id))
remove_index :lists, column: :board_id if index_exists?(:lists, :board_id)
remove_index :milestones, column: :project_id if index_exists?(:milestones, :project_id)
remove_index :notes, column: :project_id if index_exists?(:notes, :project_id)
@@ -24,7 +24,7 @@ class RemoveUnnecessaryIndexes < ActiveRecord::Migration
add_concurrent_index :award_emoji, :user_id
add_concurrent_index :ci_builds, :commit_id
add_concurrent_index :deployments, :project_id
- add_concurrent_index :deployments, ["project_id", "environment_id"]
+ add_concurrent_index :deployments, %w(project_id environment_id)
add_concurrent_index :lists, :board_id
add_concurrent_index :milestones, :project_id
add_concurrent_index :notes, :project_id
diff --git a/db/migrate/20161207231620_fixup_environment_name_uniqueness.rb b/db/migrate/20161207231620_fixup_environment_name_uniqueness.rb
index b74552e762d..a20a903a752 100644
--- a/db/migrate/20161207231620_fixup_environment_name_uniqueness.rb
+++ b/db/migrate/20161207231620_fixup_environment_name_uniqueness.rb
@@ -42,10 +42,10 @@ class FixupEnvironmentNameUniqueness < ActiveRecord::Migration
conflicts.each do |id, name|
update_sql =
Arel::UpdateManager.new(ActiveRecord::Base).
- table(environments).
- set(environments[:name] => name + "-" + id.to_s).
- where(environments[:id].eq(id)).
- to_sql
+ table(environments).
+ set(environments[:name] => name + "-" + id.to_s).
+ where(environments[:id].eq(id)).
+ to_sql
connection.exec_update(update_sql, self.class.name, [])
end
diff --git a/db/migrate/20170123061730_add_notified_of_own_activity_to_users.rb b/db/migrate/20170123061730_add_notified_of_own_activity_to_users.rb
new file mode 100644
index 00000000000..f90637e1e35
--- /dev/null
+++ b/db/migrate/20170123061730_add_notified_of_own_activity_to_users.rb
@@ -0,0 +1,14 @@
+class AddNotifiedOfOwnActivityToUsers < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+
+ def up
+ add_column_with_default :users, :notified_of_own_activity, :boolean, default: false
+ end
+
+ def down
+ remove_column :users, :notified_of_own_activity
+ end
+end
diff --git a/db/migrate/20170204172458_add_name_to_route.rb b/db/migrate/20170204172458_add_name_to_route.rb
new file mode 100644
index 00000000000..38ed1ad9039
--- /dev/null
+++ b/db/migrate/20170204172458_add_name_to_route.rb
@@ -0,0 +1,12 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddNameToRoute < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :routes, :name, :string
+ end
+end
diff --git a/db/migrate/20170206115204_add_column_ghost_to_users.rb b/db/migrate/20170206115204_add_column_ghost_to_users.rb
new file mode 100644
index 00000000000..cc1eeda1160
--- /dev/null
+++ b/db/migrate/20170206115204_add_column_ghost_to_users.rb
@@ -0,0 +1,11 @@
+class AddColumnGhostToUsers < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def up
+ add_column :users, :ghost, :boolean
+ end
+
+ def down
+ remove_column :users, :ghost
+ end
+end
diff --git a/db/migrate/20170210062829_add_index_to_labels_for_title_and_project.rb b/db/migrate/20170210062829_add_index_to_labels_for_title_and_project.rb
new file mode 100644
index 00000000000..f922ed209aa
--- /dev/null
+++ b/db/migrate/20170210062829_add_index_to_labels_for_title_and_project.rb
@@ -0,0 +1,12 @@
+class AddIndexToLabelsForTitleAndProject < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def change
+ add_concurrent_index :labels, :title
+ add_concurrent_index :labels, :project_id
+ end
+end
diff --git a/db/migrate/20170210075922_add_index_to_ci_trigger_requests_for_commit_id.rb b/db/migrate/20170210075922_add_index_to_ci_trigger_requests_for_commit_id.rb
new file mode 100644
index 00000000000..61e49c14fc0
--- /dev/null
+++ b/db/migrate/20170210075922_add_index_to_ci_trigger_requests_for_commit_id.rb
@@ -0,0 +1,11 @@
+class AddIndexToCiTriggerRequestsForCommitId < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def change
+ add_concurrent_index :ci_trigger_requests, :commit_id
+ end
+end
diff --git a/db/migrate/20170210103609_add_index_to_user_agent_detail.rb b/db/migrate/20170210103609_add_index_to_user_agent_detail.rb
new file mode 100644
index 00000000000..c01753cfbd2
--- /dev/null
+++ b/db/migrate/20170210103609_add_index_to_user_agent_detail.rb
@@ -0,0 +1,14 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddIndexToUserAgentDetail < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def change
+ add_concurrent_index(:user_agent_details, [:subject_id, :subject_type])
+ end
+end
diff --git a/db/migrate/20170214084746_add_default_artifacts_expiration_to_application_settings.rb b/db/migrate/20170214084746_add_default_artifacts_expiration_to_application_settings.rb
new file mode 100644
index 00000000000..e0e3ff8957a
--- /dev/null
+++ b/db/migrate/20170214084746_add_default_artifacts_expiration_to_application_settings.rb
@@ -0,0 +1,11 @@
+class AddDefaultArtifactsExpirationToApplicationSettings < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :application_settings,
+ :default_artifacts_expire_in, :string,
+ null: false, default: '0'
+ end
+end
diff --git a/db/migrate/20170216135621_add_index_for_latest_successful_pipeline.rb b/db/migrate/20170216135621_add_index_for_latest_successful_pipeline.rb
new file mode 100644
index 00000000000..7b1e687977b
--- /dev/null
+++ b/db/migrate/20170216135621_add_index_for_latest_successful_pipeline.rb
@@ -0,0 +1,10 @@
+class AddIndexForLatestSuccessfulPipeline < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def change
+ add_concurrent_index(:ci_commits, [:gl_project_id, :ref, :status])
+ end
+end
diff --git a/db/migrate/20170216141440_drop_index_for_builds_project_status.rb b/db/migrate/20170216141440_drop_index_for_builds_project_status.rb
new file mode 100644
index 00000000000..906711b9f3f
--- /dev/null
+++ b/db/migrate/20170216141440_drop_index_for_builds_project_status.rb
@@ -0,0 +1,8 @@
+class DropIndexForBuildsProjectStatus < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+ DOWNTIME = false
+
+ def change
+ remove_index(:ci_commits, [:gl_project_id, :status])
+ end
+end
diff --git a/db/post_migrate/20161109150329_fix_project_records_with_invalid_visibility.rb b/db/post_migrate/20161109150329_fix_project_records_with_invalid_visibility.rb
index df38591a333..14b5ef476f0 100644
--- a/db/post_migrate/20161109150329_fix_project_records_with_invalid_visibility.rb
+++ b/db/post_migrate/20161109150329_fix_project_records_with_invalid_visibility.rb
@@ -14,15 +14,15 @@ class FixProjectRecordsWithInvalidVisibility < ActiveRecord::Migration
finder_sql =
projects.
- join(namespaces, Arel::Nodes::InnerJoin).
- on(projects[:namespace_id].eq(namespaces[:id])).
- where(projects[:visibility_level].gt(namespaces[:visibility_level])).
- project(projects[:id], namespaces[:visibility_level]).
- take(BATCH_SIZE).
- to_sql
+ join(namespaces, Arel::Nodes::InnerJoin).
+ on(projects[:namespace_id].eq(namespaces[:id])).
+ where(projects[:visibility_level].gt(namespaces[:visibility_level])).
+ project(projects[:id], namespaces[:visibility_level]).
+ take(BATCH_SIZE).
+ to_sql
# Update matching rows in batches. Each batch can cause up to 3 UPDATE
- # statements, in addition to the SELECT: one per visibility_level
+ # statements, in addition to the SELECT: one per visibility_level
loop do
to_update = connection.exec_query(finder_sql)
break if to_update.rows.count == 0
diff --git a/db/post_migrate/20161221153951_rename_reserved_project_names.rb b/db/post_migrate/20161221153951_rename_reserved_project_names.rb
index 282837be1fa..49a6bc884a8 100644
--- a/db/post_migrate/20161221153951_rename_reserved_project_names.rb
+++ b/db/post_migrate/20161221153951_rename_reserved_project_names.rb
@@ -37,7 +37,7 @@ class RenameReservedProjectNames < ActiveRecord::Migration
unsubscribes
update
users
- wikis)
+ wikis).freeze
def up
queues = Array.new(THREAD_COUNT) { Queue.new }
diff --git a/db/post_migrate/20170206040400_remove_inactive_default_email_services.rb b/db/post_migrate/20170206040400_remove_inactive_default_email_services.rb
new file mode 100644
index 00000000000..a8e63e8bc7d
--- /dev/null
+++ b/db/post_migrate/20170206040400_remove_inactive_default_email_services.rb
@@ -0,0 +1,41 @@
+class RemoveInactiveDefaultEmailServices < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ Gitlab::Database.with_connection_pool(2) do |pool|
+ threads = []
+
+ threads << Thread.new do
+ pool.with_connection do |connection|
+ connection.execute <<-SQL.strip_heredoc
+ DELETE FROM services
+ WHERE type = 'BuildsEmailService'
+ AND active IS FALSE
+ AND properties = '{"notify_only_broken_builds":true}';
+ SQL
+ end
+ end
+
+ threads << Thread.new do
+ pool.with_connection do |connection|
+ connection.execute <<-SQL.strip_heredoc
+ DELETE FROM services
+ WHERE type = 'PipelinesEmailService'
+ AND active IS FALSE
+ AND properties = '{"notify_only_broken_pipelines":true}';
+ SQL
+ end
+ end
+
+ threads.each(&:join)
+ end
+ end
+
+ def down
+ # Nothing can be done to restore the records
+ end
+end
diff --git a/db/post_migrate/20170211073944_disable_invalid_service_templates.rb b/db/post_migrate/20170211073944_disable_invalid_service_templates.rb
new file mode 100644
index 00000000000..84954b1ef64
--- /dev/null
+++ b/db/post_migrate/20170211073944_disable_invalid_service_templates.rb
@@ -0,0 +1,15 @@
+class DisableInvalidServiceTemplates < ActiveRecord::Migration
+ DOWNTIME = false
+
+ unless defined?(Service)
+ class Service < ActiveRecord::Base
+ self.inheritance_column = nil
+ end
+ end
+
+ def up
+ Service.where(template: true, active: true).each do |template|
+ template.update(active: false) unless template.valid?
+ end
+ end
+end
diff --git a/db/post_migrate/20170214111112_delete_deprecated_gitlab_ci_service.rb b/db/post_migrate/20170214111112_delete_deprecated_gitlab_ci_service.rb
new file mode 100644
index 00000000000..09a827d22b0
--- /dev/null
+++ b/db/post_migrate/20170214111112_delete_deprecated_gitlab_ci_service.rb
@@ -0,0 +1,15 @@
+class DeleteDeprecatedGitlabCiService < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ disable_statement_timeout
+
+ execute("DELETE FROM services WHERE type = 'GitlabCiService';")
+ end
+
+ def down
+ # noop
+ end
+end
diff --git a/db/post_migrate/20170215200045_remove_theme_id_from_users.rb b/db/post_migrate/20170215200045_remove_theme_id_from_users.rb
new file mode 100644
index 00000000000..c51646fbe52
--- /dev/null
+++ b/db/post_migrate/20170215200045_remove_theme_id_from_users.rb
@@ -0,0 +1,9 @@
+class RemoveThemeIdFromUsers < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ remove_column :users, :theme_id, :integer
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 850772ba356..1d94368f66e 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: 20170206101030) do
+ActiveRecord::Schema.define(version: 20170216141440) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -111,6 +111,7 @@ ActiveRecord::Schema.define(version: 20170206101030) do
t.boolean "plantuml_enabled"
t.integer "max_pages_size", default: 100, null: false
t.integer "terminal_max_session_time", default: 0, null: false
+ t.string "default_artifacts_expire_in", default: '0', null: false
end
create_table "audit_events", force: :cascade do |t|
@@ -251,8 +252,8 @@ ActiveRecord::Schema.define(version: 20170206101030) do
t.integer "lock_version"
end
+ add_index "ci_commits", ["gl_project_id", "ref", "status"], name: "index_ci_commits_on_gl_project_id_and_ref_and_status", using: :btree
add_index "ci_commits", ["gl_project_id", "sha"], name: "index_ci_commits_on_gl_project_id_and_sha", using: :btree
- add_index "ci_commits", ["gl_project_id", "status"], name: "index_ci_commits_on_gl_project_id_and_status", using: :btree
add_index "ci_commits", ["gl_project_id"], name: "index_ci_commits_on_gl_project_id", using: :btree
add_index "ci_commits", ["status"], name: "index_ci_commits_on_status", using: :btree
add_index "ci_commits", ["user_id"], name: "index_ci_commits_on_user_id", using: :btree
@@ -367,6 +368,8 @@ ActiveRecord::Schema.define(version: 20170206101030) do
t.integer "commit_id"
end
+ add_index "ci_trigger_requests", ["commit_id"], name: "index_ci_trigger_requests_on_commit_id", using: :btree
+
create_table "ci_triggers", force: :cascade do |t|
t.string "token"
t.integer "project_id"
@@ -579,6 +582,8 @@ ActiveRecord::Schema.define(version: 20170206101030) do
add_index "labels", ["group_id", "project_id", "title"], name: "index_labels_on_group_id_and_project_id_and_title", unique: true, using: :btree
add_index "labels", ["type", "project_id"], name: "index_labels_on_type_and_project_id", using: :btree
+ add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree
+ add_index "labels", ["title"], name: "index_labels_on_title", using: :btree
create_table "lfs_objects", force: :cascade do |t|
t.string "oid", null: false
@@ -1037,6 +1042,7 @@ ActiveRecord::Schema.define(version: 20170206101030) do
t.string "path", null: false
t.datetime "created_at"
t.datetime "updated_at"
+ t.string "name"
end
add_index "routes", ["path"], name: "index_routes_on_path", unique: true, using: :btree
@@ -1213,6 +1219,8 @@ ActiveRecord::Schema.define(version: 20170206101030) do
t.datetime "updated_at", null: false
end
+ add_index "user_agent_details", ["subject_id", "subject_type"], name: "index_user_agent_details_on_subject_id_and_subject_type", using: :btree
+
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
@@ -1233,7 +1241,6 @@ ActiveRecord::Schema.define(version: 20170206101030) do
t.string "linkedin", default: "", null: false
t.string "twitter", default: "", null: false
t.string "authentication_token"
- t.integer "theme_id", default: 1, null: false
t.string "bio"
t.integer "failed_attempts", default: 0
t.datetime "locked_at"
@@ -1271,9 +1278,11 @@ ActiveRecord::Schema.define(version: 20170206101030) do
t.datetime "otp_grace_period_started_at"
t.boolean "ldap_email", default: false, null: false
t.boolean "external", default: false
- t.string "organization"
t.string "incoming_email_token"
+ t.string "organization"
t.boolean "authorized_projects_populated"
+ t.boolean "notified_of_own_activity", default: false, null: false
+ t.boolean "ghost"
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
diff --git a/doc/README.md b/doc/README.md
index 78030ce4e72..46a1ed0e148 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -21,6 +21,7 @@
- [Profile Settings](profile/README.md)
- [Project Services](user/project/integrations//project_services.md) Integrate a project with external services, such as CI and chat.
- [Public access](public_access/public_access.md) Learn how you can allow public and internal access to projects.
+- [Snippets](user/snippets.md) Snippets allow you to create little bits of code.
- [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects.
- [Webhooks](user/project/integrations/webhooks.md) Let GitLab notify you when new code has been pushed to your project.
- [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN.
@@ -50,13 +51,14 @@
- [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed.
- [Update](update/README.md) Update guides to upgrade your installation.
- [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page.
+- [Header logo](customization/branded_page_and_email_header.md) Change the logo on the overall page and email header.
- [Reply by email](administration/reply_by_email.md) Allow users to comment on issues and merge requests by replying to notification emails.
- [Migrate GitLab CI to CE/EE](migrate_ci_to_ce/README.md) Follow this guide to migrate your existing GitLab CI data to GitLab CE/EE.
- [Git LFS configuration](workflow/lfs/lfs_administration.md)
- [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast.
- [GitLab Pages configuration](administration/pages/index.md) Configure GitLab Pages.
- [GitLab performance monitoring with InfluxDB](administration/monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics.
-- [GitLab performance monitoring with Prometheus](administration/monitoring/performance/prometheus.md) Configure GitLab and Prometheus for measuring performance metrics.
+- [GitLab performance monitoring with Prometheus](administration/monitoring/prometheus/index.md) Configure GitLab and Prometheus for measuring performance metrics.
- [Request Profiling](administration/monitoring/performance/request_profiling.md) Get a detailed profile on slow requests.
- [Monitoring uptime](user/admin_area/monitoring/health_check.md) Check the server status using the health check endpoint.
- [Debugging Tips](administration/troubleshooting/debug.md) Tips to debug problems when things go wrong
diff --git a/doc/administration/auth/authentiq.md b/doc/administration/auth/authentiq.md
index 3f39539da95..fb1a16b0f96 100644
--- a/doc/administration/auth/authentiq.md
+++ b/doc/administration/auth/authentiq.md
@@ -54,7 +54,7 @@ Authentiq will generate a Client ID and the accompanying Client Secret for you t
5. The `scope` is set to request the user's name, email (required and signed), and permission to send push notifications to sign in on subsequent visits.
See [OmniAuth Authentiq strategy](https://github.com/AuthentiqID/omniauth-authentiq#scopes-and-redirect-uri-configuration) for more information on scopes and modifiers.
-6. Change 'YOUR_CLIENT_ID' and 'YOUR_CLIENT_SECRET' to the Client credentials you received in step 1.
+6. Change `YOUR_CLIENT_ID` and `YOUR_CLIENT_SECRET` to the Client credentials you received in step 1.
7. Save the configuration file.
diff --git a/doc/administration/build_artifacts.md b/doc/administration/build_artifacts.md
index cca422892ec..623a5321f32 100644
--- a/doc/administration/build_artifacts.md
+++ b/doc/administration/build_artifacts.md
@@ -1,96 +1 @@
-# Build artifacts administration
-
->**Notes:**
->- Introduced in GitLab 8.2 and GitLab Runner 0.7.0.
->- Starting from GitLab 8.4 and GitLab Runner 1.0, the artifacts archive format
- changed to `ZIP`.
->- This is the administration documentation. For the user guide see
- [user/project/builds/artifacts.md](../user/project/builds/artifacts.md).
-
-Artifacts is a list of files and directories which are attached to a build
-after it completes successfully. This feature is enabled by default in all
-GitLab installations. Keep reading if you want to know how to disable it.
-
-## Disabling build artifacts
-
-To disable artifacts site-wide, follow the steps below.
-
----
-
-**In Omnibus installations:**
-
-1. Edit `/etc/gitlab/gitlab.rb` and add the following line:
-
- ```ruby
- gitlab_rails['artifacts_enabled'] = false
- ```
-
-1. Save the file and [reconfigure GitLab][] for the changes to take effect.
-
----
-
-**In installations from source:**
-
-1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following lines:
-
- ```yaml
- artifacts:
- enabled: false
- ```
-
-1. Save the file and [restart GitLab][] for the changes to take effect.
-
-## Storing build artifacts
-
-After a successful build, GitLab Runner uploads an archive containing the build
-artifacts to GitLab.
-
-To change the location where the artifacts are stored, follow the steps below.
-
----
-
-**In Omnibus installations:**
-
-_The artifacts are stored by default in
-`/var/opt/gitlab/gitlab-rails/shared/artifacts`._
-
-1. To change the storage path for example to `/mnt/storage/artifacts`, edit
- `/etc/gitlab/gitlab.rb` and add the following line:
-
- ```ruby
- gitlab_rails['artifacts_path'] = "/mnt/storage/artifacts"
- ```
-
-1. Save the file and [reconfigure GitLab][] for the changes to take effect.
-
----
-
-**In installations from source:**
-
-_The artifacts are stored by default in
-`/home/git/gitlab/shared/artifacts`._
-
-1. To change the storage path for example to `/mnt/storage/artifacts`, edit
- `/home/git/gitlab/config/gitlab.yml` and add or amend the following lines:
-
- ```yaml
- artifacts:
- enabled: true
- path: /mnt/storage/artifacts
- ```
-
-1. Save the file and [restart GitLab][] for the changes to take effect.
-
-## Set the maximum file size of the artifacts
-
-Provided the artifacts are enabled, you can change the maximum file size of the
-artifacts through the [Admin area settings](../user/admin_area/settings/continuous_integration.md#maximum-artifacts-size).
-
-[reconfigure gitlab]: restart_gitlab.md "How to restart GitLab"
-[restart gitlab]: restart_gitlab.md "How to restart GitLab"
-
-## Storage statistics
-
-You can see the total storage used for build artifacts on groups and projects
-in the administration area, as well as through the [groups](../api/groups.md)
-and [projects APIs](../api/projects.md).
+This document was moved to [job_artifacts](job_artifacts.md).
diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md
new file mode 100644
index 00000000000..7b0610ae414
--- /dev/null
+++ b/doc/administration/job_artifacts.md
@@ -0,0 +1,114 @@
+# Jobs artifacts administration
+
+>**Notes:**
+>- Introduced in GitLab 8.2 and GitLab Runner 0.7.0.
+>- Starting with GitLab 8.4 and GitLab Runner 1.0, the artifacts archive format
+ changed to `ZIP`.
+>- Starting with GitLab 8.17, builds are renamed to jobs.
+>- This is the administration documentation. For the user guide see
+ [pipelines/job_artifacts](../user/project/pipelines/job_artifacts.md).
+
+Artifacts is a list of files and directories which are attached to a job
+after it completes successfully. This feature is enabled by default in all
+GitLab installations. Keep reading if you want to know how to disable it.
+
+## Disabling job artifacts
+
+To disable artifacts site-wide, follow the steps below.
+
+---
+
+**In Omnibus installations:**
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the following line:
+
+ ```ruby
+ gitlab_rails['artifacts_enabled'] = false
+ ```
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+
+---
+
+**In installations from source:**
+
+1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following lines:
+
+ ```yaml
+ artifacts:
+ enabled: false
+ ```
+
+1. Save the file and [restart GitLab][] for the changes to take effect.
+
+## Storing job artifacts
+
+After a successful job, GitLab Runner uploads an archive containing the job
+artifacts to GitLab.
+
+To change the location where the artifacts are stored, follow the steps below.
+
+---
+
+**In Omnibus installations:**
+
+_The artifacts are stored by default in
+`/var/opt/gitlab/gitlab-rails/shared/artifacts`._
+
+1. To change the storage path for example to `/mnt/storage/artifacts`, edit
+ `/etc/gitlab/gitlab.rb` and add the following line:
+
+ ```ruby
+ gitlab_rails['artifacts_path'] = "/mnt/storage/artifacts"
+ ```
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+
+---
+
+**In installations from source:**
+
+_The artifacts are stored by default in
+`/home/git/gitlab/shared/artifacts`._
+
+1. To change the storage path for example to `/mnt/storage/artifacts`, edit
+ `/home/git/gitlab/config/gitlab.yml` and add or amend the following lines:
+
+ ```yaml
+ artifacts:
+ enabled: true
+ path: /mnt/storage/artifacts
+ ```
+
+1. Save the file and [restart GitLab][] for the changes to take effect.
+
+## Set the maximum file size of the artifacts
+
+Provided the artifacts are enabled, you can change the maximum file size of the
+artifacts through the [Admin area settings](../user/admin_area/settings/continuous_integration.md#maximum-artifacts-size).
+
+## Storage statistics
+
+You can see the total storage used for job artifacts on groups and projects
+in the administration area, as well as through the [groups](../api/groups.md)
+and [projects APIs](../api/projects.md).
+
+## Implementation details
+
+When GitLab receives an artifacts archive, an archive metadata file is also
+generated. This metadata file describes all the entries that are located in the
+artifacts archive itself. The metadata file is in a binary format, with
+additional GZIP compression.
+
+GitLab does not extract the artifacts archive in order to save space, memory
+and disk I/O. It instead inspects the metadata file which contains all the
+relevant information. This is especially important when there is a lot of
+artifacts, or an archive is a very large file.
+
+When clicking on a specific file, [GitLab Workhorse] extracts it
+from the archive and the download begins. This implementation saves space,
+memory and disk I/O.
+
+[reconfigure gitlab]: restart_gitlab.md "How to restart GitLab"
+[restart gitlab]: restart_gitlab.md "How to restart GitLab"
+[gitlab workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse "GitLab Workhorse repository"
diff --git a/doc/administration/monitoring/performance/introduction.md b/doc/administration/monitoring/performance/introduction.md
index 8b106e89cc2..17c2b4b70d3 100644
--- a/doc/administration/monitoring/performance/introduction.md
+++ b/doc/administration/monitoring/performance/introduction.md
@@ -15,7 +15,7 @@ documents in order to understand and properly configure GitLab Performance Monit
>**Note:**
Omnibus GitLab 8.16 includes Prometheus as an additional tool to collect
metrics. It will eventually replace InfluxDB when their metrics collection is
-on par. Read more in the [Prometheus documentation](prometheus.md).
+on par. Read more in the [Prometheus documentation](../prometheus/index.md).
## Introduction to GitLab Performance Monitoring
diff --git a/doc/administration/monitoring/performance/prometheus.md b/doc/administration/monitoring/performance/prometheus.md
index 51c63325064..d73ef5d1789 100644
--- a/doc/administration/monitoring/performance/prometheus.md
+++ b/doc/administration/monitoring/performance/prometheus.md
@@ -1,102 +1 @@
-# GitLab Prometheus
-
->**Notes:**
-- Prometheus and the node exporter are bundled in the Omnibus GitLab package
- since GitLab 8.16. For installations from source you will have to install
- them yourself. Over subsequent releases additional GitLab metrics will be
- captured.
-- Prometheus services are off by default but will be on starting with GitLab 9.0.
-
-[Prometheus] is a powerful time-series monitoring service, providing a flexible
-platform for monitoring GitLab and other software products.
-GitLab provides out of the box monitoring with Prometheus, providing easy
-access to high quality time-series monitoring of GitLab services.
-
-## Overview
-
-Prometheus works by periodically connecting to data sources and collecting their
-performance metrics. To view and work with the monitoring data, you can either
-connect directly to Prometheus or utilize a dashboard tool like [Grafana].
-
-## Configuring Prometheus
-
->**Note:**
-Available since Omnibus GitLab 8.16. For installations from source you'll
-have to install and configure it yourself.
-
-To enable Prometheus:
-
-1. Edit `/etc/gitlab/gitlab.rb`
-1. Find and uncomment the following line, making sure it's set to `true`:
-
- ```ruby
- prometheus['enable'] = true
- ```
-
-1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
- take effect
-
-By default, Prometheus will run as the `gitlab-prometheus` user and listen on
-TCP port `9090` under localhost. If the [node exporter](#node-exporter) service
-has been enabled, it will automatically be set up as a monitoring target for
-Prometheus.
-
-## Viewing Performance Metrics
-
-After you have [enabled Prometheus](#configuring-prometheus), you can visit
-`<your_domain_name>:9090` for the dashboard that Prometheus offers by default.
-
-The performance data collected by Prometheus can be viewed directly in the
-Prometheus console or through a compatible dashboard tool.
-The Prometheus interface provides a [flexible query language][prom-query] to work
-with the collected data where you can visualize their output.
-For a more fully featured dashboard, Grafana can be used and has
-[official support for Prometheus][prom-grafana].
-
-## Prometheus exporters
-
-There are a number of libraries and servers which help in exporting existing
-metrics from third-party systems as Prometheus metrics. This is useful for cases
-where it is not feasible to instrument a given system with Prometheus metrics
-directly (for example, HAProxy or Linux system stats). You can read more in the
-[Prometheus exporters and integrations documentation][prom-exporters].
-
-While you can use any exporter you like with your GitLab installation, the
-following ones documented here are bundled in the Omnibus GitLab packages
-making it easy to configure and use.
-
-### Node exporter
-
->**Note:**
-Available since Omnibus GitLab 8.16. For installations from source you'll
-have to install and configure it yourself.
-
-The [node exporter] allows you to measure various machine resources such as
-memory, disk and CPU utilization.
-
-To enable the node exporter:
-
-1. [Enable Prometheus](#configuring-prometheus)
-1. Edit `/etc/gitlab/gitlab.rb`
-1. Find and uncomment the following line, making sure it's set to `true`:
-
- ```ruby
- node_exporter['enable'] = true
- ```
-
-1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
- take effect
-
-Prometheus it will now automatically begin collecting performance data from
-the node exporter. You can visit `<your_domain_name>:9100/metrics` for a real
-time representation of the metrics that are collected. Refresh the page and
-you will see the data change.
-
-[grafana]: https://grafana.net
-[node exporter]: https://github.com/prometheus/node_exporter
-[prometheus]: https://prometheus.io
-[prom-query]: https://prometheus.io/docs/querying/basics
-[prom-grafana]: https://prometheus.io/docs/visualization/grafana/
-[scrape-config]: https://prometheus.io/docs/operating/configuration/#%3Cscrape_config%3E
-[prom-exporters]: https://prometheus.io/docs/instrumenting/exporters/
-[reconfigure]: ../../restart_gitlab.md#omnibus-gitlab-reconfigure
+This document was moved to [monitoring/prometheus](../prometheus/index.md).
diff --git a/doc/administration/monitoring/prometheus/gitlab_monitor_exporter.md b/doc/administration/monitoring/prometheus/gitlab_monitor_exporter.md
new file mode 100644
index 00000000000..edb9c911aac
--- /dev/null
+++ b/doc/administration/monitoring/prometheus/gitlab_monitor_exporter.md
@@ -0,0 +1,30 @@
+# GitLab monitor exporter
+
+>**Note:**
+Available since [Omnibus GitLab 8.17][1132]. For installations from source
+you'll have to install and configure it yourself.
+
+The [GitLab monitor exporter] allows you to measure various GitLab metrics.
+
+To enable the GitLab monitor exporter:
+
+1. [Enable Prometheus](index.md#configuring-prometheus)
+1. Edit `/etc/gitlab/gitlab.rb`
+1. Add or find and uncomment the following line, making sure it's set to `true`:
+
+ ```ruby
+ gitlab_monitor['enable'] = true
+ ```
+
+1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
+ take effect
+
+Prometheus will now automatically begin collecting performance data from
+the GitLab monitor exporter exposed under `localhost:9168`.
+
+[← Back to the main Prometheus page](index.md)
+
+[1132]: https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/1132
+[GitLab monitor exporter]: https://gitlab.com/gitlab-org/gitlab-monitor
+[prometheus]: https://prometheus.io
+[reconfigure]: ../../restart_gitlab.md#omnibus-gitlab-reconfigure
diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md
new file mode 100644
index 00000000000..3a394c561db
--- /dev/null
+++ b/doc/administration/monitoring/prometheus/index.md
@@ -0,0 +1,147 @@
+# GitLab Prometheus
+
+>**Notes:**
+- Prometheus and the various exporters listed in this page are bundled in the
+ Omnibus GitLab package. Check each exporter's documentation for the timeline
+ they got added. For installations from source you will have to install
+ them yourself. Over subsequent releases additional GitLab metrics will be
+ captured.
+- Prometheus services are off by default but will be on starting with GitLab 9.0.
+- Prometheus and its exporters do not authenticate users, and will be available
+ to anyone who can access them.
+
+[Prometheus] is a powerful time-series monitoring service, providing a flexible
+platform for monitoring GitLab and other software products.
+GitLab provides out of the box monitoring with Prometheus, providing easy
+access to high quality time-series monitoring of GitLab services.
+
+## Overview
+
+Prometheus works by periodically connecting to data sources and collecting their
+performance metrics via the [various exporters](#prometheus-exporters). To view
+and work with the monitoring data, you can either
+[connect directly to Prometheus](#viewing-performance-metrics) or utilize a
+dashboard tool like [Grafana].
+
+## Configuring Prometheus
+
+>**Note:**
+Available since Omnibus GitLab 8.16. For installations from source you'll
+have to install and configure it yourself.
+
+To enable Prometheus:
+
+1. Edit `/etc/gitlab/gitlab.rb`
+1. Add or find and uncomment the following line, making sure it's set to `true`:
+
+ ```ruby
+ prometheus['enable'] = true
+ ```
+
+1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
+ take effect
+
+By default, Prometheus will run as the `gitlab-prometheus` user and listen on
+`http://localhost:9090`. If the [node exporter](#node-exporter) service
+has been enabled, it will automatically be set up as a monitoring target for
+Prometheus.
+
+## Changing the port Prometheus listens on
+
+>**Note:**
+The following change was added in [GitLab Omnibus 8.17][1261]. Although possible,
+it's not recommended to change the default address and port Prometheus listens
+on as this might affect or conflict with other services running on the GitLab
+server. Proceed at your own risk.
+
+To change the address/port that Prometheus listens on:
+
+1. Edit `/etc/gitlab/gitlab.rb`
+1. Add or find and uncomment the following line:
+
+ ```ruby
+ prometheus['listen_address'] = 'localhost:9090'
+ ```
+
+ Replace `localhost:9090` with the address/port you want Prometheus to
+ listen on.
+
+1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
+ take effect
+
+## Viewing performance metrics
+
+After you have [enabled Prometheus](#configuring-prometheus), you can visit
+`http://localhost:9090` for the dashboard that Prometheus offers by default.
+
+>**Note:**
+If SSL has been enabled on your GitLab instance, you may not be able to access
+Prometheus on the same browser as GitLab due to [HSTS][hsts]. We plan to
+[provide access via GitLab][multi-user-prometheus], but in the interim there are
+some workarounds: using a separate browser for Prometheus, resetting HSTS, or
+having [Nginx proxy it][nginx-custom-config]. Follow issue [#27069] for more
+information.
+
+The performance data collected by Prometheus can be viewed directly in the
+Prometheus console or through a compatible dashboard tool.
+The Prometheus interface provides a [flexible query language][prom-query] to work
+with the collected data where you can visualize their output.
+For a more fully featured dashboard, Grafana can be used and has
+[official support for Prometheus][prom-grafana].
+
+Sample Prometheus queries:
+
+- **% Memory used:** `(1 - ((node_memory_MemFree + node_memory_Cached) / node_memory_MemTotal)) * 100`
+- **% CPU load:** `1 - rate(node_cpu{mode="idle"}[5m])`
+- **Data transmitted:** `irate(node_network_transmit_bytes[5m])`
+- **Data received:** `irate(node_network_receive_bytes[5m])`
+
+## Prometheus exporters
+
+There are a number of libraries and servers which help in exporting existing
+metrics from third-party systems as Prometheus metrics. This is useful for cases
+where it is not feasible to instrument a given system with Prometheus metrics
+directly (for example, HAProxy or Linux system stats). You can read more in the
+[Prometheus exporters and integrations upstream documentation][prom-exporters].
+
+While you can use any exporter you like with your GitLab installation, the
+following ones documented here are bundled in the Omnibus GitLab packages
+making it easy to configure and use.
+
+### Node exporter
+
+The node exporter allows you to measure various machine resources such as
+memory, disk and CPU utilization.
+
+[➔ Read more about the node exporter.](node_exporter.md)
+
+### Redis exporter
+
+The Redis exporter allows you to measure various Redis metrics.
+
+[➔ Read more about the Redis exporter.](redis_exporter.md)
+
+### Postgres exporter
+
+The Postgres exporter allows you to measure various PostgreSQL metrics.
+
+[➔ Read more about the Postgres exporter.](postgres_exporter.md)
+
+### GitLab monitor exporter
+
+The GitLab monitor exporter allows you to measure various GitLab metrics.
+
+[➔ Read more about the GitLab monitor exporter.](gitlab_monitor_exporter.md)
+
+[grafana]: https://grafana.net
+[hsts]: https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security
+[multi-user-prometheus]: https://gitlab.com/gitlab-org/multi-user-prometheus
+[nginx-custom-config]: https://docs.gitlab.com/omnibus/settings/nginx.html#inserting-custom-nginx-settings-into-the-gitlab-server-block
+[prometheus]: https://prometheus.io
+[prom-exporters]: https://prometheus.io/docs/instrumenting/exporters/
+[prom-query]: https://prometheus.io/docs/querying/basics
+[prom-grafana]: https://prometheus.io/docs/visualization/grafana/
+[scrape-config]: https://prometheus.io/docs/operating/configuration/#%3Cscrape_config%3E
+[reconfigure]: ../../restart_gitlab.md#omnibus-gitlab-reconfigure
+[#27069]: https://gitlab.com/gitlab-org/gitlab-ce/issues/27069
+[1261]: https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/1261
diff --git a/doc/administration/monitoring/prometheus/node_exporter.md b/doc/administration/monitoring/prometheus/node_exporter.md
new file mode 100644
index 00000000000..aef7758a88f
--- /dev/null
+++ b/doc/administration/monitoring/prometheus/node_exporter.md
@@ -0,0 +1,30 @@
+# Node exporter
+
+>**Note:**
+Available since Omnibus GitLab 8.16. For installations from source you'll
+have to install and configure it yourself.
+
+The [node exporter] allows you to measure various machine resources such as
+memory, disk and CPU utilization.
+
+To enable the node exporter:
+
+1. [Enable Prometheus](index.md#configuring-prometheus)
+1. Edit `/etc/gitlab/gitlab.rb`
+1. Add or find and uncomment the following line, making sure it's set to `true`:
+
+ ```ruby
+ node_exporter['enable'] = true
+ ```
+
+1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
+ take effect
+
+Prometheus will now automatically begin collecting performance data from
+the node exporter exposed under `localhost:9100`.
+
+[← Back to the main Prometheus page](index.md)
+
+[node exporter]: https://github.com/prometheus/node_exporter
+[prometheus]: https://prometheus.io
+[reconfigure]: ../../restart_gitlab.md#omnibus-gitlab-reconfigure
diff --git a/doc/administration/monitoring/prometheus/postgres_exporter.md b/doc/administration/monitoring/prometheus/postgres_exporter.md
new file mode 100644
index 00000000000..8e2d3162f88
--- /dev/null
+++ b/doc/administration/monitoring/prometheus/postgres_exporter.md
@@ -0,0 +1,30 @@
+# Postgres exporter
+
+>**Note:**
+Available since [Omnibus GitLab 8.17][1131]. For installations from source
+you'll have to install and configure it yourself.
+
+The [postgres exporter] allows you to measure various PostgreSQL metrics.
+
+To enable the postgres exporter:
+
+1. [Enable Prometheus](index.md#configuring-prometheus)
+1. Edit `/etc/gitlab/gitlab.rb`
+1. Add or find and uncomment the following line, making sure it's set to `true`:
+
+ ```ruby
+ postgres_exporter['enable'] = true
+ ```
+
+1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
+ take effect
+
+Prometheus will now automatically begin collecting performance data from
+the postgres exporter exposed under `localhost:9187`.
+
+[← Back to the main Prometheus page](index.md)
+
+[1131]: https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/1131
+[postgres exporter]: https://github.com/wrouesnel/postgres_exporter
+[prometheus]: https://prometheus.io
+[reconfigure]: ../../restart_gitlab.md#omnibus-gitlab-reconfigure
diff --git a/doc/administration/monitoring/prometheus/redis_exporter.md b/doc/administration/monitoring/prometheus/redis_exporter.md
new file mode 100644
index 00000000000..d54d409dbb6
--- /dev/null
+++ b/doc/administration/monitoring/prometheus/redis_exporter.md
@@ -0,0 +1,33 @@
+# Redis exporter
+
+>**Note:**
+Available since [Omnibus GitLab 8.17][1118]. For installations from source
+you'll have to install and configure it yourself.
+
+The [Redis exporter] allows you to measure various [Redis] metrics. For more
+information on what's exported [read the upstream documentation][redis-exp].
+
+To enable the Redis exporter:
+
+1. [Enable Prometheus](index.md#configuring-prometheus)
+1. Edit `/etc/gitlab/gitlab.rb`
+1. Add or find and uncomment the following line, making sure it's set to `true`:
+
+ ```ruby
+ redis_exporter['enable'] = true
+ ```
+
+1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
+ take effect
+
+Prometheus will now automatically begin collecting performance data from
+the Redis exporter exposed under `localhost:9121`.
+
+[← Back to the main Prometheus page](index.md)
+
+[1118]: https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/1118
+[redis]: https://redis.io
+[redis exporter]: https://github.com/oliver006/redis_exporter
+[redis-exp]: https://github.com/oliver006/redis_exporter/blob/master/README.md#whats-exported
+[prometheus]: https://prometheus.io
+[reconfigure]: ../../restart_gitlab.md#omnibus-gitlab-reconfigure
diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md
index c352caf1115..62b0468da79 100644
--- a/doc/administration/pages/index.md
+++ b/doc/administration/pages/index.md
@@ -1,4 +1,4 @@
-# GitLab Pages Administration
+# GitLab Pages administration
> **Notes:**
- [Introduced][ee-80] in GitLab EE 8.3.
@@ -6,6 +6,7 @@
- GitLab Pages [were ported][ce-14605] to Community Edition in GitLab 8.17.
- This guide is for Omnibus GitLab installations. If you have installed
GitLab from source, follow the [Pages source installation document](source.md).
+- To learn how to use GitLab Pages, read the [user documentation][pages-userguide].
---
@@ -14,9 +15,6 @@ sure to read the [changelog](#changelog) if you are upgrading to a new GitLab
version as it may include new features and changes needed to be made in your
configuration.
-If you are looking for ways to upload your static content in GitLab Pages, you
-probably want to read the [user documentation][pages-userguide].
-
## Overview
GitLab Pages makes use of the [GitLab Pages daemon], a simple HTTP server
@@ -28,22 +26,24 @@ it works.
---
-In the case of custom domains, the Pages daemon needs to listen on ports `80`
-and/or `443`. For that reason, there is some flexibility in the way which you
-can set it up:
+In the case of [custom domains](#custom-domains) (but not
+[wildcard domains](#wildcard-domains)), the Pages daemon needs to listen on
+ports `80` and/or `443`. For that reason, there is some flexibility in the way
+which you can set it up:
-1. Run the pages daemon in the same server as GitLab, listening on a secondary IP
-1. Run the pages daemon in a separate server. In that case, the
+1. Run the Pages daemon in the same server as GitLab, listening on a secondary IP.
+1. Run the Pages daemon in a separate server. In that case, the
[Pages path](#change-storage-path) must also be present in the server that
- the pages daemon is installed, so you will have to share it via network.
-1. Run the pages daemon in the same server as GitLab, listening on the same IP
+ the Pages daemon is installed, so you will have to share it via network.
+1. Run the Pages daemon in the same server as GitLab, listening on the same IP
but on different ports. In that case, you will have to proxy the traffic with
a loadbalancer. If you choose that route note that you should use TCP load
balancing for HTTPS. If you use TLS-termination (HTTPS-load balancing) the
pages will not be able to be served with user provided certificates. For
HTTP it's OK to use HTTP or TCP load balancing.
-In this document, we will proceed assuming the first option.
+In this document, we will proceed assuming the first option. If you are not
+supporting custom domains a secondary IP is not needed.
## Prerequisites
@@ -54,8 +54,9 @@ Before proceeding with the Pages configuration, you will need to:
1. Configure a **wildcard DNS record**.
1. (Optional) Have a **wildcard certificate** for that domain if you decide to
serve Pages under HTTPS.
-1. (Optional but recommended) Enable [Shared runners](../ci/runners/README.md)
+1. (Optional but recommended) Enable [Shared runners](../../ci/runners/README.md)
so that your users don't have to bring their own.
+1. (Only for custom domains) Have a **secondary IP**.
### DNS configuration
@@ -64,11 +65,11 @@ you need to add a [wildcard DNS A record][wiki-wildcard-dns] pointing to the
host that GitLab runs. For example, an entry would look like this:
```
-*.example.io. 1800 IN A 1.2.3.4
+*.example.io. 1800 IN A 1.1.1.1
```
where `example.io` is the domain under which GitLab Pages will be served
-and `1.2.3.4` is the IP address of your GitLab instance.
+and `1.1.1.1` is the IP address of your GitLab instance.
> **Note:**
You should not use the GitLab domain to serve user pages. For more information
@@ -78,69 +79,46 @@ see the [security section](#security).
## Configuration
-Depending on your needs, you can install GitLab Pages in four different ways.
+Depending on your needs, you can set up GitLab Pages in 4 different ways.
+The following options are listed from the easiest setup to the most
+advanced one. The absolute minimum requirement is to set up the wildcard DNS
+since that is needed in all configurations.
-### Option 1. Custom domains with HTTPS support
+### Wildcard domains
-| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP |
-| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:|
-| `https://page.example.io` and `https://page.com` | yes | redirects to HTTPS | yes | yes |
+>**Requirements:**
+- [Wildcard DNS setup](#dns-configuration)
+>
+>---
+>
+URL scheme: `http://page.example.io`
-Pages enabled, daemon is enabled AND pages has external IP support enabled.
-In that case, the pages daemon is running, NGINX still proxies requests to
-the daemon but the daemon is also able to receive requests from the outside
-world. Custom domains and TLS are supported.
+This is the minimum setup that you can use Pages with. It is the base for all
+other setups as described below. Nginx will proxy all requests to the daemon.
+The Pages daemon doesn't listen to the outside world.
-1. Edit `/etc/gitlab/gitlab.rb`:
+1. Set the external URL for GitLab Pages in `/etc/gitlab/gitlab.rb`:
```ruby
- pages_external_url "https://example.io"
- nginx['listen_addresses'] = ['1.1.1.1']
- pages_nginx['enable'] = false
- gitlab_pages['cert'] = "/etc/gitlab/ssl/example.io.crt"
- gitlab_pages['cert_key'] = "/etc/gitlab/ssl/example.io.key"
- gitlab_pages['external_http'] = '1.1.1.2:80'
- gitlab_pages['external_https'] = '1.1.1.2:443'
+ pages_external_url 'http://example.io'
```
- where `1.1.1.1` is the primary IP address that GitLab is listening to and
- `1.1.1.2` the secondary IP where the GitLab Pages daemon listens to.
-
1. [Reconfigure GitLab][reconfigure]
-### Option 2. Custom domains without HTTPS support
-
-| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP |
-| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:|
-| `http://page.example.io` and `http://page.com` | no | yes | no | yes |
-
-Pages enabled, daemon is enabled AND pages has external IP support enabled.
-In that case, the pages daemon is running, NGINX still proxies requests to
-the daemon but the daemon is also able to receive requests from the outside
-world. Custom domains and TLS are supported.
-
-1. Edit `/etc/gitlab/gitlab.rb`:
-
- ```ruby
- pages_external_url "http://example.io"
- nginx['listen_addresses'] = ['1.1.1.1']
- pages_nginx['enable'] = false
- gitlab_pages['external_http'] = '1.1.1.2:80'
- ```
+Watch the [video tutorial][video-admin] for this configuration.
- where `1.1.1.1` is the primary IP address that GitLab is listening to and
- `1.1.1.2` the secondary IP where the GitLab Pages daemon listens to.
+### Wildcard domains with TLS support
-1. [Reconfigure GitLab][reconfigure]
+>**Requirements:**
+- [Wildcard DNS setup](#dns-configuration)
+- Wildcard TLS certificate
+>
+>---
+>
+URL scheme: `https://page.example.io`
-### Option 3. Wildcard HTTPS domain without custom domains
-
-| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP |
-| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:|
-| `https://page.example.io` | yes | no | no | no |
-
-Pages enabled, daemon is enabled and NGINX will proxy all requests to the
-daemon. Pages daemon doesn't listen to the outside world.
+Nginx will proxy all requests to the daemon. Pages daemon doesn't listen to the
+outside world.
1. Place the certificate and key inside `/etc/gitlab/ssl`
1. In `/etc/gitlab/gitlab.rb` specify the following configuration:
@@ -158,21 +136,71 @@ daemon. Pages daemon doesn't listen to the outside world.
1. [Reconfigure GitLab][reconfigure]
-### Option 4. Wildcard HTTP domain without custom domains
+## Advanced configuration
-| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP |
-| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:|
-| `http://page.example.io` | no | no | no | no |
+In addition to the wildcard domains, you can also have the option to configure
+GitLab Pages to work with custom domains. Again, there are two options here:
+support custom domains with and without TLS certificates. The easiest setup is
+that without TLS certificates.
-Pages enabled, daemon is enabled and NGINX will proxy all requests to the
-daemon. Pages daemon doesn't listen to the outside world.
+### Custom domains
-1. Set the external URL for GitLab Pages in `/etc/gitlab/gitlab.rb`:
+>**Requirements:**
+- [Wildcard DNS setup](#dns-configuration)
+- Secondary IP
+>
+---
+>
+URL scheme: `http://page.example.io` and `http://domain.com`
+
+In that case, the Pages daemon is running, Nginx still proxies requests to
+the daemon but the daemon is also able to receive requests from the outside
+world. Custom domains are supported, but no TLS.
+
+1. Edit `/etc/gitlab/gitlab.rb`:
```ruby
- pages_external_url 'http://example.io'
+ pages_external_url "http://example.io"
+ nginx['listen_addresses'] = ['1.1.1.1']
+ pages_nginx['enable'] = false
+ gitlab_pages['external_http'] = '1.1.1.2:80'
```
+ where `1.1.1.1` is the primary IP address that GitLab is listening to and
+ `1.1.1.2` the secondary IP where the GitLab Pages daemon listens to.
+
+1. [Reconfigure GitLab][reconfigure]
+
+### Custom domains with TLS support
+
+>**Requirements:**
+- [Wildcard DNS setup](#dns-configuration)
+- Wildcard TLS certificate
+- Secondary IP
+>
+---
+>
+URL scheme: `https://page.example.io` and `https://domain.com`
+
+In that case, the Pages daemon is running, Nginx still proxies requests to
+the daemon but the daemon is also able to receive requests from the outside
+world. Custom domains and TLS are supported.
+
+1. Edit `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ pages_external_url "https://example.io"
+ nginx['listen_addresses'] = ['1.1.1.1']
+ pages_nginx['enable'] = false
+ gitlab_pages['cert'] = "/etc/gitlab/ssl/example.io.crt"
+ gitlab_pages['cert_key'] = "/etc/gitlab/ssl/example.io.key"
+ gitlab_pages['external_http'] = '1.1.1.2:80'
+ gitlab_pages['external_https'] = '1.1.1.2:443'
+ ```
+
+ where `1.1.1.1` is the primary IP address that GitLab is listening to and
+ `1.1.1.2` the secondary IP where the GitLab Pages daemon listens to.
+
1. [Reconfigure GitLab][reconfigure]
## Change storage path
@@ -236,7 +264,7 @@ latest previous version.
[8-3-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-3-stable-ee/doc/pages/administration.md
[8-5-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-5-stable-ee/doc/pages/administration.md
[8-17-docs]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-17-stable-ce/doc/administration/pages/index.md
-[backup]: ../raketasks/backup_restore.md
+[backup]: ../../raketasks/backup_restore.md
[ce-14605]: https://gitlab.com/gitlab-org/gitlab-ce/issues/14605
[ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80
[ee-173]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173
@@ -244,6 +272,7 @@ latest previous version.
[NGINX configs]: https://gitlab.com/gitlab-org/gitlab-ee/tree/8-5-stable-ee/lib/support/nginx
[pages-readme]: https://gitlab.com/gitlab-org/gitlab-pages/blob/master/README.md
[pages-userguide]: ../../user/project/pages/index.md
-[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
-[restart]: ../administration/restart_gitlab.md#installations-from-source
+[reconfigure]: ../restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart]: ../restart_gitlab.md#installations-from-source
[gitlab-pages]: https://gitlab.com/gitlab-org/gitlab-pages/tree/v0.2.4
+[video-admin]: https://youtu.be/dD8c7WNcc6s
diff --git a/doc/administration/pages/source.md b/doc/administration/pages/source.md
index d4468b99992..463715e48ca 100644
--- a/doc/administration/pages/source.md
+++ b/doc/administration/pages/source.md
@@ -17,22 +17,54 @@ Pages to the latest supported version.
## Prerequisites
-[Read the Omnibus prerequisites section.](index.md#prerequisites)
+Before proceeding with the Pages configuration, you will need to:
+
+1. Have a separate domain under which the GitLab Pages will be served. In this
+ document we assume that to be `example.io`.
+1. Configure a **wildcard DNS record**.
+1. (Optional) Have a **wildcard certificate** for that domain if you decide to
+ serve Pages under HTTPS.
+1. (Optional but recommended) Enable [Shared runners](../../ci/runners/README.md)
+ so that your users don't have to bring their own.
+
+### DNS configuration
+
+GitLab Pages expect to run on their own virtual host. In your DNS server/provider
+you need to add a [wildcard DNS A record][wiki-wildcard-dns] pointing to the
+host that GitLab runs. For example, an entry would look like this:
+
+```
+*.example.io. 1800 IN A 1.1.1.1
+```
+
+where `example.io` is the domain under which GitLab Pages will be served
+and `1.1.1.1` is the IP address of your GitLab instance.
+
+> **Note:**
+You should not use the GitLab domain to serve user pages. For more information
+see the [security section](#security).
+
+[wiki-wildcard-dns]: https://en.wikipedia.org/wiki/Wildcard_DNS_record
## Configuration
-Depending on your needs, you can install GitLab Pages in four different ways.
+Depending on your needs, you can set up GitLab Pages in 4 different ways.
+The following options are listed from the easiest setup to the most
+advanced one. The absolute minimum requirement is to set up the wildcard DNS
+since that is needed in all configurations.
-### Option 1. Custom domains with HTTPS support
+### Wildcard domains
-| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP |
-| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:|
-| `https://page.example.io` and `https://page.com` | yes | redirects to HTTPS | yes | yes |
+>**Requirements:**
+- [Wildcard DNS setup](#dns-configuration)
+>
+>---
+>
+URL scheme: `http://page.example.io`
-Pages enabled, daemon is enabled AND pages has external IP support enabled.
-In that case, the pages daemon is running, NGINX still proxies requests to
-the daemon but the daemon is also able to receive requests from the outside
-world. Custom domains and TLS are supported.
+This is the minimum setup that you can use Pages with. It is the base for all
+other setups as described below. Nginx will proxy all requests to the daemon.
+The Pages daemon doesn't listen to the outside world.
1. Install the Pages daemon:
@@ -44,10 +76,14 @@ world. Custom domains and TLS are supported.
sudo -u git -H make
```
-1. Edit `gitlab.yml` to look like the example below. You need to change the
- `host` to the FQDN under which GitLab Pages will be served. Set
- `external_http` and `external_https` to the secondary IP on which the pages
- daemon will listen for connections:
+1. Go to the GitLab installation directory:
+
+ ```bash
+ cd /home/git/gitlab
+ ```
+
+1. Edit `gitlab.yml` and under the `pages` setting, set `enabled` to `true` and
+ the `host` to the FQDN under which GitLab Pages will be served:
```yaml
## GitLab Pages
@@ -57,25 +93,10 @@ world. Custom domains and TLS are supported.
# path: shared/pages
host: example.io
- port: 443
- https: true
-
- external_http: 1.1.1.2:80
- external_https: 1.1.1.2:443
+ port: 80
+ https: false
```
-1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in
- order to enable the pages daemon. In `gitlab_pages_options` the
- `-pages-domain`, `-listen-http` and `-listen-https` must match the `host`,
- `external_http` and `external_https` settings that you set above respectively.
- The `-root-cert` and `-root-key` settings are the wildcard TLS certificates
- of the `example.io` domain:
-
- ```
- gitlab_pages_enabled=true
- gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.2:80 -listen-https 1.1.1.2:443 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key
- ```
-
1. Copy the `gitlab-pages-ssl` Nginx configuration file:
```bash
@@ -85,22 +106,21 @@ world. Custom domains and TLS are supported.
Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL.
-1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace
- `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab
- listens to.
1. Restart NGINX
1. [Restart GitLab][restart]
-### Option 2. Custom domains without HTTPS support
+### Wildcard domains with TLS support
-| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP |
-| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:|
-| `http://page.example.io` and `http://page.com` | no | yes | no | yes |
+>**Requirements:**
+- [Wildcard DNS setup](#dns-configuration)
+- Wildcard TLS certificate
+>
+>---
+>
+URL scheme: `https://page.example.io`
-Pages enabled, daemon is enabled AND pages has external IP support enabled.
-In that case, the pages daemon is running, NGINX still proxies requests to
-the daemon but the daemon is also able to receive requests from the outside
-world. Custom domains and TLS are supported.
+Nginx will proxy all requests to the daemon. Pages daemon doesn't listen to the
+outside world.
1. Install the Pages daemon:
@@ -112,34 +132,20 @@ world. Custom domains and TLS are supported.
sudo -u git -H make
```
-1. Edit `gitlab.yml` to look like the example below. You need to change the
- `host` to the FQDN under which GitLab Pages will be served. Set
- `external_http` to the secondary IP on which the pages daemon will listen
- for connections:
+1. In `gitlab.yml`, set the port to `443` and https to `true`:
- ```yaml
+ ```bash
+ ## GitLab Pages
pages:
enabled: true
# The location where pages are stored (default: shared/pages).
# path: shared/pages
host: example.io
- port: 80
- https: false
-
- external_http: 1.1.1.2:80
+ port: 443
+ https: true
```
-1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in
- order to enable the pages daemon. In `gitlab_pages_options` the
- `-pages-domain` and `-listen-http` must match the `host` and `external_http`
- settings that you set above respectively:
-
- ```
- gitlab_pages_enabled=true
- gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.2:80"
- ```
-
1. Copy the `gitlab-pages-ssl` Nginx configuration file:
```bash
@@ -149,20 +155,30 @@ world. Custom domains and TLS are supported.
Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL.
-1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace
- `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab
- listens to.
1. Restart NGINX
1. [Restart GitLab][restart]
-### Option 3. Wildcard HTTPS domain without custom domains
-| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP |
-| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:|
-| `https://page.example.io` | yes | no | no | no |
+## Advanced configuration
+
+In addition to the wildcard domains, you can also have the option to configure
+GitLab Pages to work with custom domains. Again, there are two options here:
+support custom domains with and without TLS certificates. The easiest setup is
+that without TLS certificates.
+
+### Custom domains
-Pages enabled, daemon is enabled and NGINX will proxy all requests to the
-daemon. Pages daemon doesn't listen to the outside world.
+>**Requirements:**
+- [Wildcard DNS setup](#dns-configuration)
+- Secondary IP
+>
+---
+>
+URL scheme: `http://page.example.io` and `http://domain.com`
+
+In that case, the pages daemon is running, Nginx still proxies requests to
+the daemon but the daemon is also able to receive requests from the outside
+world. Custom domains are supported, but no TLS.
1. Install the Pages daemon:
@@ -173,20 +189,35 @@ daemon. Pages daemon doesn't listen to the outside world.
sudo -u git -H git checkout v0.2.4
sudo -u git -H make
```
-1. In `gitlab.yml`, set the port to `443` and https to `true`:
- ```bash
- ## GitLab Pages
+1. Edit `gitlab.yml` to look like the example below. You need to change the
+ `host` to the FQDN under which GitLab Pages will be served. Set
+ `external_http` to the secondary IP on which the pages daemon will listen
+ for connections:
+
+ ```yaml
pages:
enabled: true
# The location where pages are stored (default: shared/pages).
# path: shared/pages
host: example.io
- port: 443
- https: true
+ port: 80
+ https: false
+
+ external_http: 1.1.1.2:80
```
+1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in
+ order to enable the pages daemon. In `gitlab_pages_options` the
+ `-pages-domain` and `-listen-http` must match the `host` and `external_http`
+ settings that you set above respectively:
+
+ ```
+ gitlab_pages_enabled=true
+ gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.2:80"
+ ```
+
1. Copy the `gitlab-pages-ssl` Nginx configuration file:
```bash
@@ -196,17 +227,26 @@ daemon. Pages daemon doesn't listen to the outside world.
Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL.
+1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace
+ `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab
+ listens to.
1. Restart NGINX
1. [Restart GitLab][restart]
-### Option 4. Wildcard HTTP domain without custom domains
+### Custom domains with TLS support
-| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP |
-| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:|
-| `http://page.example.io` | no | no | no | no |
+>**Requirements:**
+- [Wildcard DNS setup](#dns-configuration)
+- Wildcard TLS certificate
+- Secondary IP
+>
+---
+>
+URL scheme: `https://page.example.io` and `https://domain.com`
-Pages enabled, daemon is enabled and NGINX will proxy all requests to the
-daemon. Pages daemon doesn't listen to the outside world.
+In that case, the pages daemon is running, Nginx still proxies requests to
+the daemon but the daemon is also able to receive requests from the outside
+world. Custom domains and TLS are supported.
1. Install the Pages daemon:
@@ -218,14 +258,10 @@ daemon. Pages daemon doesn't listen to the outside world.
sudo -u git -H make
```
-1. Go to the GitLab installation directory:
-
- ```bash
- cd /home/git/gitlab
- ```
-
-1. Edit `gitlab.yml` and under the `pages` setting, set `enabled` to `true` and
- the `host` to the FQDN under which GitLab Pages will be served:
+1. Edit `gitlab.yml` to look like the example below. You need to change the
+ `host` to the FQDN under which GitLab Pages will be served. Set
+ `external_http` and `external_https` to the secondary IP on which the pages
+ daemon will listen for connections:
```yaml
## GitLab Pages
@@ -235,10 +271,25 @@ daemon. Pages daemon doesn't listen to the outside world.
# path: shared/pages
host: example.io
- port: 80
- https: false
+ port: 443
+ https: true
+
+ external_http: 1.1.1.2:80
+ external_https: 1.1.1.2:443
```
+1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in
+ order to enable the pages daemon. In `gitlab_pages_options` the
+ `-pages-domain`, `-listen-http` and `-listen-https` must match the `host`,
+ `external_http` and `external_https` settings that you set above respectively.
+ The `-root-cert` and `-root-key` settings are the wildcard TLS certificates
+ of the `example.io` domain:
+
+ ```
+ gitlab_pages_enabled=true
+ gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.2:80 -listen-https 1.1.1.2:443 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key
+ ```
+
1. Copy the `gitlab-pages-ssl` Nginx configuration file:
```bash
@@ -248,9 +299,27 @@ daemon. Pages daemon doesn't listen to the outside world.
Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL.
+1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace
+ `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab
+ listens to.
1. Restart NGINX
1. [Restart GitLab][restart]
+## Change storage path
+
+Follow the steps below to change the default path where GitLab Pages' contents
+are stored.
+
+1. Pages are stored by default in `/var/opt/gitlab/gitlab-rails/shared/pages`.
+ If you wish to store them in another location you must set it up in
+ `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ gitlab_rails['pages_path'] = "/mnt/storage/pages"
+ ```
+
+1. [Reconfigure GitLab][reconfigure]
+
## NGINX caveats
>**Note:**
@@ -311,13 +380,13 @@ Pages are part of the [regular backup][backup] so there is nothing to configure.
You should strongly consider running GitLab pages under a different hostname
than GitLab to prevent XSS attacks.
-[backup]: ../raketasks/backup_restore.md
+[backup]: ../../raketasks/backup_restore.md
[ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80
[ee-173]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173
[gitlab pages daemon]: https://gitlab.com/gitlab-org/gitlab-pages
[NGINX configs]: https://gitlab.com/gitlab-org/gitlab-ee/tree/8-5-stable-ee/lib/support/nginx
[pages-readme]: https://gitlab.com/gitlab-org/gitlab-pages/blob/master/README.md
[pages-userguide]: ../../user/project/pages/index.md
-[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
-[restart]: ../administration/restart_gitlab.md#installations-from-source
+[reconfigure]: ../restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart]: ../restart_gitlab.md#installations-from-source
[gitlab-pages]: https://gitlab.com/gitlab-org/gitlab-pages/tree/v0.2.4
diff --git a/doc/administration/reply_by_email.md b/doc/administration/reply_by_email.md
index 00494e7e9d6..4f5c22e2d29 100644
--- a/doc/administration/reply_by_email.md
+++ b/doc/administration/reply_by_email.md
@@ -69,7 +69,9 @@ please consult [RFC 5322](https://tools.ietf.org/html/rfc5322#section-3.6.4).
If you want to use Gmail / Google Apps with Reply by email, make sure you have
[IMAP access enabled](https://support.google.com/mail/troubleshooter/1668960?hl=en#ts=1665018)
-and [allowed less secure apps to access the account](https://support.google.com/accounts/answer/6010255).
+and [allowed less secure apps to access the account](https://support.google.com/accounts/answer/6010255)
+or [turn-on 2-step validation](https://support.google.com/accounts/answer/185839)
+and use [an application password](https://support.google.com/mail/answer/185833).
To set up a basic Postfix mail server with IMAP access on Ubuntu, follow the
[Postfix setup documentation](reply_by_email_postfix_setup.md).
diff --git a/doc/api/branches.md b/doc/api/branches.md
index ffcfea41453..765ca439720 100644
--- a/doc/api/branches.md
+++ b/doc/api/branches.md
@@ -34,6 +34,8 @@ Example response:
"committer_email": "john@example.com",
"committer_name": "John Smith",
"id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
+ "short_id": "7b5c3cc",
+ "title": "add projects API",
"message": "add projects API",
"parent_ids": [
"4ad91d3c1144c406e50c7b33bae684bd6837faf8"
@@ -78,6 +80,8 @@ Example response:
"committer_email": "john@example.com",
"committer_name": "John Smith",
"id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
+ "short_id": "7b5c3cc",
+ "title": "add projects API",
"message": "add projects API",
"parent_ids": [
"4ad91d3c1144c406e50c7b33bae684bd6837faf8"
@@ -119,6 +123,8 @@ Example response:
"committer_email": "john@example.com",
"committer_name": "John Smith",
"id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
+ "short_id": "7b5c3cc",
+ "title": "add projects API",
"message": "add projects API",
"parent_ids": [
"4ad91d3c1144c406e50c7b33bae684bd6837faf8"
@@ -163,6 +169,8 @@ Example response:
"committer_email": "john@example.com",
"committer_name": "John Smith",
"id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
+ "short_id": "7b5c3cc",
+ "title": "add projects API",
"message": "add projects API",
"parent_ids": [
"4ad91d3c1144c406e50c7b33bae684bd6837faf8"
@@ -185,11 +193,11 @@ POST /projects/:id/repository/branches
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project |
-| `branch_name` | string | yes | The name of the branch |
+| `branch` | string | yes | The name of the branch |
| `ref` | string | yes | The branch name or commit SHA to create branch from |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/branches?branch_name=newbranch&ref=master"
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/branches?branch=newbranch&ref=master"
```
Example response:
@@ -204,6 +212,8 @@ Example response:
"committer_email": "john@example.com",
"committer_name": "John Smith",
"id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
+ "short_id": "7b5c3cc",
+ "title": "add projects API",
"message": "add projects API",
"parent_ids": [
"4ad91d3c1144c406e50c7b33bae684bd6837faf8"
diff --git a/doc/api/commits.md b/doc/api/commits.md
index 53ce381c8ae..18bc2873678 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -12,8 +12,8 @@ GET /projects/:id/repository/commits
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
| `ref_name` | string | no | The name of a repository branch or tag or if not given the default branch |
-| `since` | string | no | Only commits after or in this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ |
-| `until` | string | no | Only commits before or in this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ |
+| `since` | string | no | Only commits after or on this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ |
+| `until` | string | no | Only commits before or on this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/commits"
@@ -29,11 +29,15 @@ Example response:
"title": "Replace sanitize with escape once",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dzaporozhets@sphereconsultinginc.com",
+ "authored_date": "2012-09-20T11:50:22+03:00",
"committer_name": "Administrator",
"committer_email": "admin@example.com",
+ "committed_date": "2012-09-20T11:50:22+03:00",
"created_at": "2012-09-20T11:50:22+03:00",
"message": "Replace sanitize with escape once",
- "allow_failure": false
+ "parent_ids": [
+ "6104942438c14ec7bd21c6cd5bd995272b3faff6"
+ ]
},
{
"id": "6104942438c14ec7bd21c6cd5bd995272b3faff6",
@@ -45,7 +49,9 @@ Example response:
"committer_email": "dmitriy.zaporozhets@gmail.com",
"created_at": "2012-09-20T09:06:12+03:00",
"message": "Sanitize for network graph",
- "allow_failure": false
+ "parent_ids": [
+ "ae1d9fb46aa2b07ee9836d49862ec4e2c46fbbba"
+ ]
}
]
```
@@ -63,7 +69,7 @@ POST /projects/:id/repository/commits
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME |
-| `branch_name` | string | yes | The name of a branch |
+| `branch` | string | yes | The name of a branch |
| `commit_message` | string | yes | Commit message |
| `actions[]` | array | yes | An array of action hashes to commit as a batch. See the next table for what attributes it can take. |
| `author_email` | string | no | Specify the commit author's email address |
@@ -81,7 +87,7 @@ POST /projects/:id/repository/commits
```bash
PAYLOAD=$(cat << 'JSON'
{
- "branch_name": "master",
+ "branch": "master",
"commit_message": "some commit message",
"actions": [
{
@@ -214,10 +220,16 @@ Example response:
"title": "Feature added",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dmitriy.zaporozhets@gmail.com",
+ "authored_date": "2016-12-12T20:10:39.000+01:00",
"created_at": "2016-12-12T20:10:39.000+01:00",
"committer_name": "Administrator",
"committer_email": "admin@example.com",
- "message": "Feature added\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n"
+ "committed_date": "2016-12-12T20:10:39.000+01:00",
+ "title": "Feature added",
+ "message": "Feature added\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
+ "parent_ids": [
+ "a738f717824ff53aebad8b090c1b79a14f2bd9e8"
+ ]
}
```
@@ -444,6 +456,7 @@ POST /projects/:id/statuses/:sha
| `name` or `context` | string | no | The label to differentiate this status from the status of other systems. Default value is `default`
| `target_url` | string | no | The target URL to associate with this status
| `description` | string | no | The short description of the status
+| `coverage` | float | no | The total code coverage
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/17/statuses/18f3e63d05582537db6d183d9d557be09e1f90c8?state=success"
@@ -464,6 +477,7 @@ Example response:
"name" : "default",
"sha" : "18f3e63d05582537db6d183d9d557be09e1f90c8",
"status" : "success",
+ "coverage": 100.0,
"description" : null,
"id" : 93,
"target_url" : null,
diff --git a/doc/api/deploy_keys.md b/doc/api/deploy_keys.md
index 284d5f88c55..39afc4b2df5 100644
--- a/doc/api/deploy_keys.md
+++ b/doc/api/deploy_keys.md
@@ -137,7 +137,7 @@ Example response:
## Delete deploy key
-Delete a deploy key from a project
+Removes a deploy key from the project. If the deploy key is used only for this project, it will be deleted from the system.
```
DELETE /projects/:id/deploy_keys/:key_id
@@ -156,14 +156,11 @@ Example response:
```json
{
- "updated_at" : "2015-08-29T12:50:57.259Z",
- "key" : "ssh-rsa AAAA...",
- "public" : false,
- "title" : "My deploy key",
- "user_id" : null,
- "created_at" : "2015-08-29T12:50:57.259Z",
- "fingerprint" : "6a:33:1f:74:51:c0:39:81:79:ec:7a:31:f8:40:20:43",
- "id" : 13
+ "id": 6,
+ "deploy_key_id": 14,
+ "project_id": 1,
+ "created_at" : "2015-08-29T12:50:57.259Z",
+ "updated_at" : "2015-08-29T12:50:57.259Z"
}
```
@@ -190,27 +187,3 @@ Example response:
"created_at" : "2015-08-29T12:44:31.550Z"
}
```
-
-## Disable a deploy key
-
-Disable a deploy key for a project. Returns the disabled key.
-
-```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/deploy_keys/13/disable
-```
-
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of the project |
-| `key_id` | integer | yes | The ID of the deploy key |
-
-Example response:
-
-```json
-{
- "key" : "ssh-rsa AAAA...",
- "id" : 12,
- "title" : "My deploy key",
- "created_at" : "2015-08-29T12:44:31.550Z"
-}
-```
diff --git a/doc/api/groups.md b/doc/api/groups.md
index 3b38e3e1bee..39adb5be502 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -14,6 +14,7 @@ Parameters:
| `order_by` | string | no | Order groups by `name` or `path`. Default is `name` |
| `sort` | string | no | Order groups in `asc` or `desc` order. Default is `asc` |
| `statistics` | boolean | no | Include group statistics (admins only) |
+| `owned` | boolean | no | Limit by groups owned by the current user |
```
GET /groups
@@ -32,27 +33,14 @@ GET /groups
"web_url": "http://localhost:3000/groups/foo-bar",
"request_access_enabled": false,
"full_name": "Foobar Group",
- "full_path": "foo-bar"
+ "full_path": "foo-bar",
+ "parent_id": null
}
]
```
You can search for groups by name or path, see below.
-## List owned groups
-
-Get a list of groups which are owned by the authenticated user.
-
-```
-GET /groups/owned
-```
-
-Parameters:
-
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `statistics` | boolean | no | Include group statistics |
-
## List a group's projects
Get a list of projects in this group.
@@ -72,6 +60,8 @@ Parameters:
| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Return list of authorized projects matching the search criteria |
| `simple` | boolean | no | Return only the ID, URL, name, and path of each project |
+| `owned` | boolean | no | Limit by projects owned by the current user |
+| `starred` | boolean | no | Limit by projects starred by the current user |
Example response:
@@ -105,15 +95,7 @@ Example response:
"id": 5,
"name": "Experimental",
"path": "h5bp",
- "owner_id": null,
- "created_at": "2016-04-05T21:40:49.152Z",
- "updated_at": "2016-04-07T08:07:48.466Z",
- "description": "foo",
- "avatar": {
- "url": null
- },
- "share_with_group_lock": false,
- "visibility_level": 10
+ "kind": "group"
},
"avatar_url": null,
"star_count": 1,
@@ -156,8 +138,9 @@ Example response:
"avatar_url": null,
"web_url": "https://gitlab.example.com/groups/twitter",
"request_access_enabled": false,
- "full_name": "Foobar Group",
- "full_path": "foo-bar",
+ "full_name": "Twitter",
+ "full_path": "twitter",
+ "parent_id": null,
"projects": [
{
"id": 7,
@@ -188,15 +171,7 @@ Example response:
"id": 4,
"name": "Twitter",
"path": "twitter",
- "owner_id": null,
- "created_at": "2016-06-17T07:47:24.216Z",
- "updated_at": "2016-06-17T07:47:24.216Z",
- "description": "Aliquid qui quis dignissimos distinctio ut commodi voluptas est.",
- "avatar": {
- "url": null
- },
- "share_with_group_lock": false,
- "visibility_level": 20
+ "kind": "group"
},
"avatar_url": null,
"star_count": 0,
@@ -235,15 +210,7 @@ Example response:
"id": 4,
"name": "Twitter",
"path": "twitter",
- "owner_id": null,
- "created_at": "2016-06-17T07:47:24.216Z",
- "updated_at": "2016-06-17T07:47:24.216Z",
- "description": "Aliquid qui quis dignissimos distinctio ut commodi voluptas est.",
- "avatar": {
- "url": null
- },
- "share_with_group_lock": false,
- "visibility_level": 20
+ "kind": "group"
},
"avatar_url": null,
"star_count": 0,
@@ -284,15 +251,7 @@ Example response:
"id": 5,
"name": "H5bp",
"path": "h5bp",
- "owner_id": null,
- "created_at": "2016-06-17T07:47:26.621Z",
- "updated_at": "2016-06-17T07:47:26.621Z",
- "description": "Id consequatur rem vel qui doloremque saepe.",
- "avatar": {
- "url": null
- },
- "share_with_group_lock": false,
- "visibility_level": 20
+ "kind": "group"
},
"avatar_url": null,
"star_count": 0,
@@ -332,6 +291,7 @@ Parameters:
- `visibility_level` (optional) - The group's visibility. 0 for private, 10 for internal, 20 for public.
- `lfs_enabled` (optional) - Enable/disable Large File Storage (LFS) for the projects in this group
- `request_access_enabled` (optional) - Allow users to request member access.
+- `parent_id` (optional) - The parent group id for creating nested group.
## Transfer project to group
@@ -383,6 +343,7 @@ Example response:
"request_access_enabled": false,
"full_name": "Foobar Group",
"full_path": "foo-bar",
+ "parent_id": null,
"projects": [
{
"id": 9,
@@ -412,15 +373,7 @@ Example response:
"id": 5,
"name": "Experimental",
"path": "h5bp",
- "owner_id": null,
- "created_at": "2016-04-05T21:40:49.152Z",
- "updated_at": "2016-04-07T08:07:48.466Z",
- "description": "foo",
- "avatar": {
- "url": null
- },
- "share_with_group_lock": false,
- "visibility_level": 10
+ "kind": "group"
},
"avatar_url": null,
"star_count": 1,
diff --git a/doc/api/issues.md b/doc/api/issues.md
index 7c0a444d4fa..5266077e098 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -30,7 +30,7 @@ GET /issues?milestone=1.0.0&state=opened
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `state` | string | no | Return all issues or just those that are `opened` or `closed`|
-| `labels` | string | no | Comma-separated list of label names, issues with any of the labels will be returned |
+| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned |
| `milestone` | string| no | The milestone title |
| `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` |
@@ -188,7 +188,7 @@ GET /projects/:id/issues?milestone=1.0.0&state=opened
| `id` | integer | yes | The ID of a project |
| `iid` | integer | no | Return the issue having the given `iid` |
| `state` | string | no | Return all issues or just those that are `opened` or `closed`|
-| `labels` | string | no | Comma-separated list of label names, issues with any of the labels will be returned |
+| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned |
| `milestone` | string| no | The milestone title |
| `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` |
@@ -514,7 +514,7 @@ If the user is already subscribed to the issue, the status code `304`
is returned.
```
-POST /projects/:id/issues/:issue_id/subscription
+POST /projects/:id/issues/:issue_id/subscribe
```
| Attribute | Type | Required | Description |
@@ -523,7 +523,7 @@ POST /projects/:id/issues/:issue_id/subscription
| `issue_id` | integer | yes | The ID of a project's issue |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/subscription
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/subscribe
```
Example response:
@@ -569,7 +569,7 @@ from it. If the user is not subscribed to the issue, the
status code `304` is returned.
```
-DELETE /projects/:id/issues/:issue_id/subscription
+POST /projects/:id/issues/:issue_id/unsubscribe
```
| Attribute | Type | Required | Description |
@@ -578,7 +578,7 @@ DELETE /projects/:id/issues/:issue_id/subscription
| `issue_id` | integer | yes | The ID of a project's issue |
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/subscription
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/unsubscribe
```
Example response:
diff --git a/doc/api/keys.md b/doc/api/keys.md
index b68f08a007d..3b55c2baf56 100644
--- a/doc/api/keys.md
+++ b/doc/api/keys.md
@@ -33,7 +33,6 @@ Parameters:
"twitter": "",
"website_url": "",
"email": "john@example.com",
- "theme_id": 2,
"color_scheme_id": 1,
"projects_limit": 10,
"current_sign_in_at": null,
diff --git a/doc/api/labels.md b/doc/api/labels.md
index 863b28c23b7..8e0855fe9e2 100644
--- a/doc/api/labels.md
+++ b/doc/api/labels.md
@@ -188,12 +188,12 @@ Example response:
## Subscribe to a label
-Subscribes the authenticated user to a label to receive notifications.
+Subscribes the authenticated user to a label to receive notifications.
If the user is already subscribed to the label, the status code `304`
is returned.
```
-POST /projects/:id/labels/:label_id/subscription
+POST /projects/:id/labels/:label_id/subscribe
```
| Attribute | Type | Required | Description |
@@ -202,7 +202,7 @@ POST /projects/:id/labels/:label_id/subscription
| `label_id` | integer or string | yes | The ID or title of a project's label |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/labels/1/subscription
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/labels/1/subscribe
```
Example response:
@@ -228,7 +228,7 @@ from it. If the user is not subscribed to the label, the
status code `304` is returned.
```
-DELETE /projects/:id/labels/:label_id/subscription
+POST /projects/:id/labels/:label_id/unsubscribe
```
| Attribute | Type | Required | Description |
@@ -237,7 +237,7 @@ DELETE /projects/:id/labels/:label_id/subscription
| `label_id` | integer or string | yes | The ID or title of a project's label |
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/labels/1/subscription
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/labels/1/unsubscribe
```
Example response:
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 1cf7632d60c..ea30a163a12 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -350,16 +350,17 @@ PUT /projects/:id/merge_requests/:merge_request_id
| --------- | ---- | -------- | ----------- |
| `id` | string | yes | The ID of a project |
| `merge_request_id` | integer | yes | The ID of a merge request |
-| `source_branch` | string | yes | The source branch |
-| `target_branch` | string | yes | The target branch |
-| `title` | string | yes | Title of MR |
+| `target_branch` | string | no | The target branch |
+| `title` | string | no | Title of MR |
| `assignee_id` | integer | no | Assignee user ID |
| `description` | string | no | Description of MR |
-| `target_project_id` | integer | no | The target project (numeric id) |
+| `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 |
+Must include at least one non-required attribute from above.
+
```json
{
"id": 1,
@@ -666,7 +667,7 @@ Subscribes the authenticated user to a merge request to receive notification. If
status code `304` is returned.
```
-POST /projects/:id/merge_requests/:merge_request_id/subscription
+POST /projects/:id/merge_requests/:merge_request_id/subscribe
```
| Attribute | Type | Required | Description |
@@ -675,7 +676,7 @@ POST /projects/:id/merge_requests/:merge_request_id/subscription
| `merge_request_id` | integer | yes | The ID of the merge request |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/17/subscription
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/17/subscribe
```
Example response:
@@ -740,7 +741,7 @@ notifications from that merge request. If the user is
not subscribed to the merge request, the status code `304` is returned.
```
-DELETE /projects/:id/merge_requests/:merge_request_id/subscription
+POST /projects/:id/merge_requests/:merge_request_id/unsubscribe
```
| Attribute | Type | Required | Description |
@@ -749,7 +750,7 @@ DELETE /projects/:id/merge_requests/:merge_request_id/subscription
| `merge_request_id` | integer | yes | The ID of the merge request |
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/17/subscription
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/17/unsubscribe
```
Example response:
diff --git a/doc/api/milestones.md b/doc/api/milestones.md
index 12497acff98..bf7dcc008e9 100644
--- a/doc/api/milestones.md
+++ b/doc/api/milestones.md
@@ -103,3 +103,16 @@ Parameters:
- `id` (required) - The ID of a project
- `milestone_id` (required) - The ID of a project milestone
+
+## Get all merge requests assigned to a single milestone
+
+Gets all merge requests assigned to a single project milestone.
+
+```
+GET /projects/:id/milestones/:milestone_id/merge_requests
+```
+
+Parameters:
+
+- `id` (required) - The ID of a project
+- `milestone_id` (required) - The ID of a project milestone \ No newline at end of file
diff --git a/doc/api/namespaces.md b/doc/api/namespaces.md
index 88cd407d792..1d97b5de688 100644
--- a/doc/api/namespaces.md
+++ b/doc/api/namespaces.md
@@ -35,6 +35,12 @@ Example response:
"id": 2,
"path": "group1",
"kind": "group"
+ },
+ {
+ "id": 3,
+ "path": "bar",
+ "kind": "group",
+ "full_path": "foo/bar",
}
]
```
@@ -64,7 +70,8 @@ Example response:
{
"id": 4,
"path": "twitter",
- "kind": "group"
+ "kind": "group",
+ "full_path": "twitter",
}
]
```
diff --git a/doc/api/notes.md b/doc/api/notes.md
index 214dfa4068d..dced821cc6d 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -34,8 +34,6 @@ Parameters:
"created_at": "2013-10-02T09:22:45Z",
"updated_at": "2013-10-02T10:22:45Z",
"system": true,
- "upvote": false,
- "downvote": false,
"noteable_id": 377,
"noteable_type": "Issue"
},
@@ -54,8 +52,6 @@ Parameters:
"created_at": "2013-10-02T09:56:03Z",
"updated_at": "2013-10-02T09:56:03Z",
"system": true,
- "upvote": false,
- "downvote": false,
"noteable_id": 121,
"noteable_type": "Issue"
}
@@ -147,9 +143,7 @@ Example Response:
"created_at": "2016-04-05T22:10:44.164Z",
"system": false,
"noteable_id": 11,
- "noteable_type": "Issue",
- "upvote": false,
- "downvote": false
+ "noteable_type": "Issue"
}
```
@@ -271,9 +265,7 @@ Example Response:
"created_at": "2016-04-06T16:51:53.239Z",
"system": false,
"noteable_id": 52,
- "noteable_type": "Snippet",
- "upvote": false,
- "downvote": false
+ "noteable_type": "Snippet"
}
```
@@ -322,8 +314,6 @@ Parameters:
"created_at": "2013-10-02T08:57:14Z",
"updated_at": "2013-10-02T08:57:14Z",
"system": false,
- "upvote": false,
- "downvote": false,
"noteable_id": 2,
"noteable_type": "MergeRequest"
}
@@ -400,8 +390,6 @@ Example Response:
"created_at": "2016-04-05T22:11:59.923Z",
"system": false,
"noteable_id": 7,
- "noteable_type": "MergeRequest",
- "upvote": false,
- "downvote": false
+ "noteable_type": "MergeRequest"
}
```
diff --git a/doc/api/oauth2.md b/doc/api/oauth2.md
index 5ef5e3f5744..eab532af594 100644
--- a/doc/api/oauth2.md
+++ b/doc/api/oauth2.md
@@ -57,7 +57,7 @@ Once you have the authorization code you can request an `access_token` using the
```
parameters = 'client_id=APP_ID&client_secret=APP_SECRET&code=RETURNED_CODE&grant_type=authorization_code&redirect_uri=REDIRECT_URI'
-RestClient.post 'http://localhost:3000/oauth/token', parameters
+RestClient.post 'http://gitlab.example.com/oauth/token', parameters
# The response will be
{
@@ -77,13 +77,13 @@ You can now make requests to the API with the access token returned.
The access token allows you to make requests to the API on a behalf of a user.
```
-GET https://localhost:3000/api/v3/user?access_token=OAUTH-TOKEN
+GET https://gitlab.example.com/api/v3/user?access_token=OAUTH-TOKEN
```
Or you can put the token to the Authorization header:
```
-curl --header "Authorization: Bearer OAUTH-TOKEN" https://localhost:3000/api/v3/user
+curl --header "Authorization: Bearer OAUTH-TOKEN" https://gitlab.example.com/api/v3/user
```
## Resource Owner Password Credentials
diff --git a/doc/api/pipelines.md b/doc/api/pipelines.md
index 82351ae688f..f3c9827f742 100644
--- a/doc/api/pipelines.md
+++ b/doc/api/pipelines.md
@@ -163,7 +163,7 @@ Example of response
}
```
-## Retry failed builds in a pipeline
+## Retry builds in a pipeline
> [Introduced][ce-5837] in GitLab 8.11
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 122075bbd11..1a8c0ae758f 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -36,6 +36,8 @@ Parameters:
| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Return list of authorized projects matching the search criteria |
| `simple` | boolean | no | Return only the ID, URL, name, and path of each project |
+| `owned` | boolean | no | Limit by projects owned by the current user |
+| `starred` | boolean | no | Limit by projects starred by the current user |
```json
[
@@ -72,13 +74,11 @@ Parameters:
"last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
"namespace": {
- "created_at": "2013-09-30T13:46:02Z",
- "description": "",
"id": 3,
"name": "Diaspora",
- "owner_id": 1,
"path": "diaspora",
- "updated_at": "2013-09-30T13:46:02Z"
+ "kind": "group",
+ "full_path": "diaspora"
},
"archived": false,
"avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png",
@@ -125,13 +125,11 @@ Parameters:
"last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
"namespace": {
- "created_at": "2013-09-30T13:46:02Z",
- "description": "",
"id": 4,
"name": "Brightbox",
- "owner_id": 1,
"path": "brightbox",
- "updated_at": "2013-09-30T13:46:02Z"
+ "kind": "group",
+ "full_path": "brightbox"
},
"permissions": {
"project_access": {
@@ -158,196 +156,6 @@ Parameters:
]
```
-Get a list of projects which the authenticated user can see.
-
-```
-GET /projects/visible
-```
-
-Parameters:
-
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `archived` | boolean | no | Limit by archived status |
-| `visibility` | string | no | Limit by visibility `public`, `internal`, or `private` |
-| `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` |
-| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` |
-| `search` | string | no | Return list of authorized projects matching the search criteria |
-| `simple` | boolean | no | Return only the ID, URL, name, and path of each project |
-
-```json
-[
- {
- "id": 4,
- "description": null,
- "default_branch": "master",
- "public": false,
- "visibility_level": 0,
- "ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git",
- "http_url_to_repo": "http://example.com/diaspora/diaspora-client.git",
- "web_url": "http://example.com/diaspora/diaspora-client",
- "tag_list": [
- "example",
- "disapora client"
- ],
- "owner": {
- "id": 3,
- "name": "Diaspora",
- "created_at": "2013-09-30T13:46:02Z"
- },
- "name": "Diaspora Client",
- "name_with_namespace": "Diaspora / Diaspora Client",
- "path": "diaspora-client",
- "path_with_namespace": "diaspora/diaspora-client",
- "issues_enabled": true,
- "open_issues_count": 1,
- "merge_requests_enabled": true,
- "builds_enabled": true,
- "wiki_enabled": true,
- "snippets_enabled": false,
- "container_registry_enabled": false,
- "created_at": "2013-09-30T13:46:02Z",
- "last_activity_at": "2013-09-30T13:46:02Z",
- "creator_id": 3,
- "namespace": {
- "created_at": "2013-09-30T13:46:02Z",
- "description": "",
- "id": 3,
- "name": "Diaspora",
- "owner_id": 1,
- "path": "diaspora",
- "updated_at": "2013-09-30T13:46:02Z"
- },
- "archived": false,
- "avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png",
- "shared_runners_enabled": true,
- "forks_count": 0,
- "star_count": 0,
- "runners_token": "b8547b1dc37721d05889db52fa2f02",
- "public_builds": true,
- "shared_with_groups": []
- },
- {
- "id": 6,
- "description": null,
- "default_branch": "master",
- "public": false,
- "visibility_level": 0,
- "ssh_url_to_repo": "git@example.com:brightbox/puppet.git",
- "http_url_to_repo": "http://example.com/brightbox/puppet.git",
- "web_url": "http://example.com/brightbox/puppet",
- "tag_list": [
- "example",
- "puppet"
- ],
- "owner": {
- "id": 4,
- "name": "Brightbox",
- "created_at": "2013-09-30T13:46:02Z"
- },
- "name": "Puppet",
- "name_with_namespace": "Brightbox / Puppet",
- "path": "puppet",
- "path_with_namespace": "brightbox/puppet",
- "issues_enabled": true,
- "open_issues_count": 1,
- "merge_requests_enabled": true,
- "builds_enabled": true,
- "wiki_enabled": true,
- "snippets_enabled": false,
- "container_registry_enabled": false,
- "created_at": "2013-09-30T13:46:02Z",
- "last_activity_at": "2013-09-30T13:46:02Z",
- "creator_id": 3,
- "namespace": {
- "created_at": "2013-09-30T13:46:02Z",
- "description": "",
- "id": 4,
- "name": "Brightbox",
- "owner_id": 1,
- "path": "brightbox",
- "updated_at": "2013-09-30T13:46:02Z"
- },
- "permissions": {
- "project_access": {
- "access_level": 10,
- "notification_level": 3
- },
- "group_access": {
- "access_level": 50,
- "notification_level": 3
- }
- },
- "archived": false,
- "avatar_url": null,
- "shared_runners_enabled": true,
- "forks_count": 0,
- "star_count": 0,
- "runners_token": "b8547b1dc37721d05889db52fa2f02",
- "public_builds": true,
- "shared_with_groups": []
- }
-]
-```
-
-### List owned projects
-
-Get a list of projects which are owned by the authenticated user.
-
-```
-GET /projects/owned
-```
-
-Parameters:
-
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `archived` | boolean | no | Limit by archived status |
-| `visibility` | string | no | Limit by visibility `public`, `internal`, or `private` |
-| `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` |
-| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` |
-| `search` | string | no | Return list of authorized projects matching the search criteria |
-| `simple` | boolean | no | Return only the ID, URL, name, and path of each project |
-| `statistics` | boolean | no | Include project statistics |
-
-### List starred projects
-
-Get a list of projects which are starred by the authenticated user.
-
-```
-GET /projects/starred
-```
-
-Parameters:
-
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `archived` | boolean | no | Limit by archived status |
-| `visibility` | string | no | Limit by visibility `public`, `internal`, or `private` |
-| `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` |
-| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` |
-| `search` | string | no | Return list of authorized projects matching the search criteria |
-| `simple` | boolean | no | Return only the ID, URL, name, and path of each project |
-
-### List ALL projects
-
-Get a list of all GitLab projects (admin only).
-
-```
-GET /projects/all
-```
-
-Parameters:
-
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `archived` | boolean | no | Limit by archived status |
-| `visibility` | string | no | Limit by visibility `public`, `internal`, or `private` |
-| `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` |
-| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` |
-| `search` | string | no | Return list of authorized projects matching the search criteria |
-| `statistics` | boolean | no | Include project statistics |
-
### Get single project
Get a specific project, identified by project ID or NAMESPACE/PROJECT_NAME, which is owned by the authenticated user.
@@ -398,13 +206,11 @@ Parameters:
"last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
"namespace": {
- "created_at": "2013-09-30T13:46:02Z",
- "description": "",
"id": 3,
"name": "Diaspora",
- "owner_id": 1,
"path": "diaspora",
- "updated_at": "2013-09-30T13:46:02Z"
+ "kind": "group",
+ "full_path": "diaspora"
},
"permissions": {
"project_access": {
@@ -601,8 +407,6 @@ Parameters:
},
"created_at": "2015-12-04T10:33:56.698Z",
"system": false,
- "upvote": false,
- "downvote": false,
"noteable_id": 377,
"noteable_type": "Issue"
},
@@ -642,7 +446,6 @@ Parameters:
| `snippets_enabled` | boolean | no | Enable snippets for this project |
| `container_registry_enabled` | boolean | no | Enable container registry for this project |
| `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
-| `public` | boolean | no | If `true`, the same as setting `visibility_level` to 20 |
| `visibility_level` | integer | no | See [project visibility level](#project-visibility-level) |
| `import_url` | string | no | URL to import repository from |
| `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members |
@@ -676,7 +479,6 @@ Parameters:
| `snippets_enabled` | boolean | no | Enable snippets for this project |
| `container_registry_enabled` | boolean | no | Enable container registry for this project |
| `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
-| `public` | boolean | no | If `true`, the same as setting `visibility_level` to 20 |
| `visibility_level` | integer | no | See [project visibility level](#project-visibility-level) |
| `import_url` | string | no | URL to import repository from |
| `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members |
@@ -709,7 +511,6 @@ Parameters:
| `snippets_enabled` | boolean | no | Enable snippets for this project |
| `container_registry_enabled` | boolean | no | Enable container registry for this project |
| `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
-| `public` | boolean | no | If `true`, the same as setting `visibility_level` to 20 |
| `visibility_level` | integer | no | See [project visibility level](#project-visibility-level) |
| `import_url` | string | no | URL to import repository from |
| `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members |
@@ -723,7 +524,7 @@ Parameters:
Forks a project into the user namespace of the authenticated user or the one provided.
```
-POST /projects/fork/:id
+POST /projects/:id/fork
```
Parameters:
@@ -782,13 +583,11 @@ Example response:
"last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
"namespace": {
- "created_at": "2013-09-30T13:46:02Z",
- "description": "",
"id": 3,
"name": "Diaspora",
- "owner_id": 1,
"path": "diaspora",
- "updated_at": "2013-09-30T13:46:02Z"
+ "kind": "group",
+ "full_path": "diaspora"
},
"archived": true,
"avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png",
@@ -808,7 +607,7 @@ Example response:
Unstars a given project. Returns status code `304` if the project is not starred.
```
-DELETE /projects/:id/star
+POST /projects/:id/unstar
```
| Attribute | Type | Required | Description |
@@ -816,7 +615,7 @@ DELETE /projects/:id/star
| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/star"
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/unstar"
```
Example response:
@@ -850,13 +649,11 @@ Example response:
"last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
"namespace": {
- "created_at": "2013-09-30T13:46:02Z",
- "description": "",
"id": 3,
"name": "Diaspora",
- "owner_id": 1,
"path": "diaspora",
- "updated_at": "2013-09-30T13:46:02Z"
+ "kind": "group",
+ "full_path": "diaspora"
},
"archived": true,
"avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png",
@@ -924,13 +721,11 @@ Example response:
"last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
"namespace": {
- "created_at": "2013-09-30T13:46:02Z",
- "description": "",
"id": 3,
"name": "Diaspora",
- "owner_id": 1,
"path": "diaspora",
- "updated_at": "2013-09-30T13:46:02Z"
+ "kind": "group",
+ "full_path": "diaspora"
},
"permissions": {
"project_access": {
@@ -1009,13 +804,11 @@ Example response:
"last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
"namespace": {
- "created_at": "2013-09-30T13:46:02Z",
- "description": "",
"id": 3,
"name": "Diaspora",
- "owner_id": 1,
"path": "diaspora",
- "updated_at": "2013-09-30T13:46:02Z"
+ "kind": "group",
+ "full_path": "diaspora"
},
"permissions": {
"project_access": {
@@ -1400,3 +1193,17 @@ Parameters:
| `query` | string | yes | A string contained in the project name |
| `order_by` | string | no | Return requests ordered by `id`, `name`, `created_at` or `last_activity_at` fields |
| `sort` | string | no | Return requests sorted in `asc` or `desc` order |
+
+## Start the Housekeeping task for a Project
+
+>**Note:** This feature was introduced in GitLab 9.0
+
+```
+POST /projects/:id/housekeeping
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md
index 73dde599b7e..677e209ccd9 100644
--- a/doc/api/repository_files.md
+++ b/doc/api/repository_files.md
@@ -46,22 +46,22 @@ POST /projects/:id/repository/files
```
```bash
-curl --request POST --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/project.rb&branch_name=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20content&commit_message=create%20a%20new%20file'
+curl --request POST --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/project.rb&branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20content&commit_message=create%20a%20new%20file'
```
Example response:
```json
{
- "file_path": "app/project.rb",
- "branch_name": "master"
+ "file_name": "app/project.rb",
+ "branch": "master"
}
```
Parameters:
- `file_path` (required) - Full path to new file. Ex. lib/class.rb
-- `branch_name` (required) - The name of branch
+- `branch` (required) - The name of branch
- `encoding` (optional) - Change encoding to 'base64'. Default is text.
- `author_email` (optional) - Specify the commit author's email address
- `author_name` (optional) - Specify the commit author's name
@@ -75,22 +75,22 @@ PUT /projects/:id/repository/files
```
```bash
-curl --request PUT --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/project.rb&branch_name=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20other%20content&commit_message=update%20file'
+curl --request PUT --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/project.rb&branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20other%20content&commit_message=update%20file'
```
Example response:
```json
{
- "file_path": "app/project.rb",
- "branch_name": "master"
+ "file_name": "app/project.rb",
+ "branch": "master"
}
```
Parameters:
- `file_path` (required) - Full path to file. Ex. lib/class.rb
-- `branch_name` (required) - The name of branch
+- `branch` (required) - The name of branch
- `encoding` (optional) - Change encoding to 'base64'. Default is text.
- `author_email` (optional) - Specify the commit author's email address
- `author_name` (optional) - Specify the commit author's name
@@ -113,22 +113,22 @@ DELETE /projects/:id/repository/files
```
```bash
-curl --request PUT --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/project.rb&branch_name=master&author_email=author%40example.com&author_name=Firstname%20Lastname&commit_message=delete%20file'
+curl --request DELETE --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/project.rb&branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&commit_message=delete%20file'
```
Example response:
```json
{
- "file_path": "app/project.rb",
- "branch_name": "master"
+ "file_name": "app/project.rb",
+ "branch": "master"
}
```
Parameters:
- `file_path` (required) - Full path to file. Ex. lib/class.rb
-- `branch_name` (required) - The name of branch
+- `branch` (required) - The name of branch
- `author_email` (optional) - Specify the commit author's email address
- `author_name` (optional) - Specify the commit author's name
- `commit_message` (required) - Commit message
diff --git a/doc/api/session.md b/doc/api/session.md
index f776424023e..d7809716fbe 100644
--- a/doc/api/session.md
+++ b/doc/api/session.md
@@ -41,7 +41,6 @@ Example response:
"twitter": "",
"website_url": "",
"email": "john@example.com",
- "theme_id": 1,
"color_scheme_id": 1,
"projects_limit": 10,
"current_sign_in_at": "2015-07-07T07:10:58.392Z",
diff --git a/doc/api/todos.md b/doc/api/todos.md
index a5e81801024..a2fbbc7e1f8 100644
--- a/doc/api/todos.md
+++ b/doc/api/todos.md
@@ -184,7 +184,7 @@ Marks a single pending todo given by its ID for the current user as done. The
todo marked as done is returned in the response.
```
-DELETE /todos/:id
+POST /todos/:id/mark_as_done
```
Parameters:
@@ -194,7 +194,7 @@ Parameters:
| `id` | integer | yes | The ID of a todo |
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/todos/130
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/todos/130/mark_as_done
```
Example Response:
@@ -277,20 +277,15 @@ Example Response:
## Mark all todos as done
-Marks all pending todos for the current user as done. It returns the number of marked todos.
+Marks all pending todos for the current user as done. It returns the HTTP status code `204` with an empty response.
```
-DELETE /todos
+POST /todos/mark_as_done
```
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/todos
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/todos/donmark_as_donee
```
-Example Response:
-
-```json
-3
-```
[ce-3188]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3188
diff --git a/doc/api/users.md b/doc/api/users.md
index fea9bdf9639..d14548e8bbb 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -72,7 +72,6 @@ GET /users
"organization": "",
"last_sign_in_at": "2012-06-01T11:41:01Z",
"confirmed_at": "2012-05-23T09:05:22Z",
- "theme_id": 1,
"color_scheme_id": 2,
"projects_limit": 100,
"current_sign_in_at": "2012-06-02T06:36:55Z",
@@ -105,7 +104,6 @@ GET /users
"organization": "",
"last_sign_in_at": null,
"confirmed_at": "2012-05-30T16:53:06.148Z",
- "theme_id": 1,
"color_scheme_id": 3,
"projects_limit": 100,
"current_sign_in_at": "2014-03-19T17:54:13Z",
@@ -198,7 +196,6 @@ Parameters:
"organization": "",
"last_sign_in_at": "2012-06-01T11:41:01Z",
"confirmed_at": "2012-05-23T09:05:22Z",
- "theme_id": 1,
"color_scheme_id": 2,
"projects_limit": 100,
"current_sign_in_at": "2012-06-02T06:36:55Z",
@@ -216,7 +213,7 @@ Parameters:
## User creation
-Creates a new user. Note only administrators can create new users.
+Creates a new user. Note only administrators can create new users. Either `password` or `reset_password` should be specified (`reset_password` takes priority).
```
POST /users
@@ -225,7 +222,8 @@ POST /users
Parameters:
- `email` (required) - Email
-- `password` (required) - Password
+- `password` (optional) - Password
+- `reset_password` (optional) - Send user password reset link - true or false(default)
- `username` (required) - Username
- `name` (required) - Name
- `skype` (optional) - Skype ID
@@ -322,7 +320,6 @@ GET /user
"organization": "",
"last_sign_in_at": "2012-06-01T11:41:01Z",
"confirmed_at": "2012-05-23T09:05:22Z",
- "theme_id": 1,
"color_scheme_id": 2,
"projects_limit": 100,
"current_sign_in_at": "2012-06-02T06:36:55Z",
@@ -368,7 +365,6 @@ GET /user
"organization": "",
"last_sign_in_at": "2012-06-01T11:41:01Z",
"confirmed_at": "2012-05-23T09:05:22Z",
- "theme_id": 1,
"color_scheme_id": 2,
"projects_limit": 100,
"current_sign_in_at": "2012-06-02T06:36:55Z",
@@ -663,14 +659,14 @@ Will return `200 OK` on success, or `404 Not found` if either user or email cann
Blocks the specified user. Available only for admin.
```
-PUT /users/:id/block
+POST /users/:id/block
```
Parameters:
- `id` (required) - id of specified user
-Will return `200 OK` on success, `404 User Not Found` is user cannot be found or
+Will return `201 OK` on success, `404 User Not Found` is user cannot be found or
`403 Forbidden` when trying to block an already blocked user by LDAP synchronization.
## Unblock user
@@ -678,14 +674,14 @@ Will return `200 OK` on success, `404 User Not Found` is user cannot be found or
Unblocks the specified user. Available only for admin.
```
-PUT /users/:id/unblock
+POST /users/:id/unblock
```
Parameters:
- `id` (required) - id of specified user
-Will return `200 OK` on success, `404 User Not Found` is user cannot be found or
+Will return `201 OK` on success, `404 User Not Found` is user cannot be found or
`403 Forbidden` when trying to unblock a user blocked by LDAP synchronization.
### Get user contribution events
@@ -816,8 +812,6 @@ Example response:
},
"created_at": "2015-12-04T10:33:56.698Z",
"system": false,
- "upvote": false,
- "downvote": false,
"noteable_id": 377,
"noteable_type": "Issue"
},
diff --git a/doc/api/v3_to_v4.md b/doc/api/v3_to_v4.md
index 707f0437b7e..8af041be234 100644
--- a/doc/api/v3_to_v4.md
+++ b/doc/api/v3_to_v4.md
@@ -4,11 +4,42 @@ Our V4 API version is currently available as *Beta*! It means that V3
will still be supported and remain unchanged for now, but be aware that the following
changes are in V4:
-### Changes
-
-- Removed `/projects/:search` (use: `/projects?search=x`)
-- `iid` filter has been removed from `projects/:id/issues`
-- `projects/:id/merge_requests?iid[]=x&iid[]=y` array filter has been renamed to `iids`
-- Endpoints under `projects/merge_request/:id` have been removed (use: `projects/merge_requests/:id`)
-- Project snippets do not return deprecated field `expires_at`
-- Endpoints under `projects/:id/keys` have been removed (use `projects/:id/deploy_keys`)
+### 8.17
+
+- Removed `/projects/:search` (use: `/projects?search=x`) [!8877](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8877)
+- `iid` filter has been removed from `projects/:id/issues` [!8967](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8967)
+- `projects/:id/merge_requests?iid[]=x&iid[]=y` array filter has been renamed to `iids` [!8793](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8793)
+- Endpoints under `projects/merge_request/:id` have been removed (use: `projects/merge_requests/:id`) [!8793](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8793)
+- Project snippets do not return deprecated field `expires_at` [!8723](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8723)
+- Endpoints under `projects/:id/keys` have been removed (use `projects/:id/deploy_keys`) [!8716](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8716)
+
+### 9.0
+
+- Status 409 returned for POST `project/:id/members` when a member already exists [!9093](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9093)
+- Moved `DELETE /projects/:id/star` to `POST /projects/:id/unstar` [!9328](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9328)
+- Removed the following deprecated Templates endpoints (these are still accessible with `/templates` prefix) [!8853](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8853)
+ - `/licences`
+ - `/licences/:key`
+ - `/gitignores`
+ - `/gitlab_ci_ymls`
+ - `/dockerfiles`
+ - `/gitignores/:key`
+ - `/gitlab_ci_ymls/:key`
+ - `/dockerfiles/:key`
+- Moved `/projects/fork/:id` to `/projects/:id/fork` [!8940](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8940)
+- Moved `DELETE /todos` to `POST /todos/mark_as_done` and `DELETE /todos/:todo_id` to `POST /todos/:todo_id/mark_as_done` [!9410](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9410)
+- Endpoints `/projects/owned`, `/projects/visible`, `/projects/starred` & `/projects/all` are consolidated into `/projects` using query parameters [!8962](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8962)
+- Return pagination headers for all endpoints that return an array [!8606](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8606)
+- Removed `DELETE projects/:id/deploy_keys/:key_id/disable`. Use `DELETE projects/:id/deploy_keys/:key_id` instead [!9366](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9366)
+- Moved `PUT /users/:id/(block|unblock)` to `POST /users/:id/(block|unblock)` [!9371](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9371)
+- Make subscription API more RESTful. Use `post ":project_id/:subscribable_type/:subscribable_id/subscribe"` to subscribe and `post ":project_id/:subscribable_type/:subscribable_id/unsubscribe"` to unsubscribe from a resource. [!9325](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9325)
+- Labels filter on `projects/:id/issues` and `/issues` now matches only issues containing all labels (i.e.: Logical AND, not OR) [!8849](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8849)
+- Renamed param `branch_name` to `branch` on the following endpoints [!8936](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8936)
+ - POST `:id/repository/branches`
+ - POST `:id/repository/commits`
+ - POST/PUT/DELETE `:id/repository/files`
+- Renamed `branch_name` to `branch` on DELETE `id/repository/branches/:branch` response [!8936](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8936)
+- Remove `public` param from create and edit actions of projects [!8736](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8736)
+- Notes do not return deprecated field `upvote` and `downvote` [!9384](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9384)
+- Remove `GET /groups/owned`. Use `GET /groups?owned=true` instead [!9505](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9505)
+- Return 202 with JSON body on async removals on V4 API (DELETE `/projects/:id/repository/merged_branches` and DELETE `/projects/:id`) [!9449](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9449) \ No newline at end of file
diff --git a/doc/ci/README.md b/doc/ci/README.md
index dd14698e9cd..cbab7c9f18d 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -2,22 +2,22 @@
## CI User documentation
-- [Get started with GitLab CI](quick_start/README.md)
+- [Getting started with GitLab CI](quick_start/README.md)
- [CI examples for various languages](examples/README.md)
- [Learn how to enable or disable GitLab CI](enable_or_disable_ci.md)
-- [Pipelines and builds](pipelines.md)
+- [Pipelines and jobs](pipelines.md)
- [Environments and deployments](environments.md)
- [Learn how `.gitlab-ci.yml` works](yaml/README.md)
-- [Configure a Runner, the application that runs your builds](runners/README.md)
+- [Configure a Runner, the application that runs your jobs](runners/README.md)
- [Use Docker images with GitLab Runner](docker/using_docker_images.md)
- [Use CI to build Docker images](docker/using_docker_build.md)
- [CI Variables](variables/README.md) - Learn how to use variables defined in
your `.gitlab-ci.yml` or secured ones defined in your project's settings
- [Use SSH keys in your build environment](ssh_keys/README.md)
-- [Trigger builds through the API](triggers/README.md)
-- [Build artifacts](../user/project/builds/artifacts.md)
+- [Trigger jobs through the API](triggers/README.md)
+- [Job artifacts](../user/project/pipelines/job_artifacts.md)
- [User permissions](../user/permissions.md#gitlab-ci)
-- [Build permissions](../user/permissions.md#build-permissions)
+- [Jobs permissions](../user/permissions.md#jobs-permissions)
- [API](../api/ci/README.md)
- [CI services (linked docker containers)](services/README.md)
- [CI/CD pipelines settings](../user/project/pipelines/settings.md)
@@ -27,6 +27,6 @@
## Breaking changes
-- [New CI build permissions model](../user/project/new_ci_build_permissions_model.md)
- Read about what changed in GitLab 8.12 and how that affects your builds.
- There's a new way to access your Git submodules and LFS objects in builds.
+- [New CI job permissions model](../user/project/new_ci_build_permissions_model.md)
+ Read about what changed in GitLab 8.12 and how that affects your jobs.
+ There's a new way to access your Git submodules and LFS objects in jobs.
diff --git a/doc/ci/build_artifacts/README.md b/doc/ci/build_artifacts/README.md
index 05605f10fb4..22b3872025f 100644
--- a/doc/ci/build_artifacts/README.md
+++ b/doc/ci/build_artifacts/README.md
@@ -1,4 +1 @@
-This document was moved to:
-
-- [user/project/builds/artifacts.md](../../user/project/builds/artifacts.md) - user guide
-- [administration/build_artifacts.md](../../administration/build_artifacts.md) - administrator guide
+This document was moved to [pipelines/job_artifacts.md](../../user/project/pipelines/job_artifacts.md).
diff --git a/doc/ci/docker/README.md b/doc/ci/docker/README.md
index 84eaf29efd1..99669a9272a 100644
--- a/doc/ci/docker/README.md
+++ b/doc/ci/docker/README.md
@@ -1,4 +1,4 @@
# Docker integration
-+ [Using Docker Images](using_docker_images.md)
-+ [Using Docker Build](using_docker_build.md) \ No newline at end of file
+- [Using Docker Images](using_docker_images.md)
+- [Using Docker Build](using_docker_build.md)
diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md
index 28141cced3b..8620984d40d 100644
--- a/doc/ci/docker/using_docker_build.md
+++ b/doc/ci/docker/using_docker_build.md
@@ -12,6 +12,7 @@ One of the new trends in Continuous Integration/Deployment is to:
1. deploy to a server from the pushed image.
It's also useful when your application already has the `Dockerfile` that can be used to create and test an image:
+
```bash
$ docker build -t my-image dockerfiles/
$ docker run my-docker-image /script/to/run/tests
@@ -19,23 +20,23 @@ $ docker tag my-image my-registry:5000/my-image
$ docker push my-registry:5000/my-image
```
-This requires special configuration of GitLab Runner to enable `docker` support during builds.
+This requires special configuration of GitLab Runner to enable `docker` support during jobs.
## Runner Configuration
-There are three methods to enable the use of `docker build` and `docker run` during builds; each with their own tradeoffs.
+There are three methods to enable the use of `docker build` and `docker run` during jobs; each with their own tradeoffs.
### Use shell executor
The simplest approach is to install GitLab Runner in `shell` execution mode.
-GitLab Runner then executes build scripts as the `gitlab-runner` user.
+GitLab Runner then executes job scripts as the `gitlab-runner` user.
1. Install [GitLab Runner](https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/#installation).
-1. During GitLab Runner installation select `shell` as method of executing build scripts or use command:
+1. During GitLab Runner installation select `shell` as method of executing job scripts or use command:
```bash
- $ sudo gitlab-ci-multi-runner register -n \
+ sudo gitlab-ci-multi-runner register -n \
--url https://gitlab.com/ci \
--registration-token REGISTRATION_TOKEN \
--executor shell \
@@ -50,16 +51,17 @@ GitLab Runner then executes build scripts as the `gitlab-runner` user.
3. Add `gitlab-runner` user to `docker` group:
```bash
- $ sudo usermod -aG docker gitlab-runner
+ sudo usermod -aG docker gitlab-runner
```
4. Verify that `gitlab-runner` has access to Docker:
```bash
- $ sudo -u gitlab-runner -H docker info
+ sudo -u gitlab-runner -H docker info
```
You can now verify that everything works by adding `docker info` to `.gitlab-ci.yml`:
+
```yaml
before_script:
- docker info
@@ -80,12 +82,12 @@ For more information please read [On Docker security: `docker` group considered
The second approach is to use the special docker-in-docker (dind)
[Docker image](https://hub.docker.com/_/docker/) with all tools installed
-(`docker` and `docker-compose`) and run the build script in context of that
+(`docker` and `docker-compose`) and run the job script in context of that
image in privileged mode.
In order to do that, follow the steps:
-1. Install [GitLab Runner](https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/#installation).
+1. Install [GitLab Runner](https://docs.gitlab.com/runner/install).
1. Register GitLab Runner from the command line to use `docker` and `privileged`
mode:
@@ -155,10 +157,10 @@ not without its own challenges:
escalation which can lead to container breakout. For more information, check
out the official Docker documentation on
[Runtime privilege and Linux capabilities][docker-cap].
-- Using docker-in-docker, each build is in a clean environment without the past
- history. Concurrent builds work fine because every build gets it's own
+- When using docker-in-docker, each job is in a clean environment without the past
+ history. Concurrent jobs work fine because every build gets it's own
instance of Docker engine so they won't conflict with each other. But this
- also means builds can be slower because there's no caching of layers.
+ also means jobs can be slower because there's no caching of layers.
- By default, `docker:dind` uses `--storage-driver vfs` which is the slowest
form offered. To use a different driver, see
[Using the overlayfs driver](#using-the-overlayfs-driver).
@@ -171,7 +173,7 @@ The third approach is to bind-mount `/var/run/docker.sock` into the container so
In order to do that, follow the steps:
-1. Install [GitLab Runner](https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/#installation).
+1. Install [GitLab Runner](https://docs.gitlab.com/runner/install).
1. Register GitLab Runner from the command line to use `docker` and share `/var/run/docker.sock`:
@@ -187,7 +189,9 @@ In order to do that, follow the steps:
The above command will register a new Runner to use the special
`docker:latest` image which is provided by Docker. **Notice that it's using
- the Docker daemon of the Runner itself, and any containers spawned by docker commands will be siblings of the Runner rather than children of the runner.** This may have complications and limitations that are unsuitable for your workflow.
+ the Docker daemon of the Runner itself, and any containers spawned by docker
+ commands will be siblings of the Runner rather than children of the runner.**
+ This may have complications and limitations that are unsuitable for your workflow.
The above command will create a `config.toml` entry similar to this:
@@ -206,7 +210,8 @@ In order to do that, follow the steps:
Insecure = false
```
-1. You can now use `docker` in the build script (note that you don't need to include the `docker:dind` service as when using the Docker in Docker executor):
+1. You can now use `docker` in the build script (note that you don't need to
+ include the `docker:dind` service as when using the Docker in Docker executor):
```yaml
image: docker:latest
@@ -221,18 +226,23 @@ In order to do that, follow the steps:
- docker run my-docker-image /script/to/run/tests
```
-While the above method avoids using Docker in privileged mode, you should be aware of the following implications:
-* By sharing the docker daemon, you are effectively disabling all
-the security mechanisms of containers and exposing your host to privilege
-escalation which can lead to container breakout. For example, if a project
-ran `docker rm -f $(docker ps -a -q)` it would remove the GitLab Runner
-containers.
-* Concurrent builds may not work; if your tests
-create containers with specific names, they may conflict with each other.
-* Sharing files and directories from the source repo into containers may not
-work as expected since volume mounting is done in the context of the host
-machine, not the build container.
-e.g. `docker run --rm -t -i -v $(pwd)/src:/home/app/src test-image:latest run_app_tests`
+While the above method avoids using Docker in privileged mode, you should be
+aware of the following implications:
+
+- By sharing the docker daemon, you are effectively disabling all
+ the security mechanisms of containers and exposing your host to privilege
+ escalation which can lead to container breakout. For example, if a project
+ ran `docker rm -f $(docker ps -a -q)` it would remove the GitLab Runner
+ containers.
+- Concurrent jobs may not work; if your tests
+ create containers with specific names, they may conflict with each other.
+- Sharing files and directories from the source repo into containers may not
+ work as expected since volume mounting is done in the context of the host
+ machine, not the build container, e.g.:
+
+ ```
+ docker run --rm -t -i -v $(pwd)/src:/home/app/src test-image:latest run_app_tests
+ ```
## Using the OverlayFS driver
@@ -298,8 +308,32 @@ push to the Registry connected to your project. Its password is provided in the
`$CI_BUILD_TOKEN` variable. This allows you to automate building and deployment
of your Docker images.
+You can also make use of [other variables](../variables/README.md) to avoid hardcoding:
+
+```yaml
+services:
+ - docker:dind
+
+variables:
+ IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_BUILD_REF_NAME
+
+before_script:
+ - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY
+
+build:
+ stage: build
+ script:
+ - docker build -t $IMAGE_TAG .
+ - docker push $IMAGE_TAG
+```
+
+Here, `$CI_REGISTRY_IMAGE` would be resolved to the address of the registry tied
+to this project, and `$CI_BUILD_REF_NAME` would be resolved to the branch or
+tag name for this particular job. We also declare our own variable, `$IMAGE_TAG`,
+combining the two to save us some typing in the `script` section.
+
Here's a more elaborate example that splits up the tasks into 4 pipeline stages,
-including two tests that run in parallel. The build is stored in the container
+including two tests that run in parallel. The `build` is stored in the container
registry and used by subsequent stages, downloading the image
when needed. Changes to `master` also get tagged as `latest` and deployed using
an application-specific deploy script:
@@ -360,17 +394,17 @@ deploy:
Some things you should be aware of when using the Container Registry:
- You must log in to the container registry before running commands. Putting
- this in `before_script` will run it before each build job.
+ this in `before_script` will run it before each job.
- Using `docker build --pull` makes sure that Docker fetches any changes to base
images before building just in case your cache is stale. It takes slightly
longer, but means you don’t get stuck without security patches to base images.
- Doing an explicit `docker pull` before each `docker run` makes sure to fetch
the latest image that was just built. This is especially important if you are
using multiple runners that cache images locally. Using the git SHA in your
- image tag makes this less necessary since each build will be unique and you
+ image tag makes this less necessary since each job will be unique and you
shouldn't ever have a stale image, but it's still possible if you re-build a
given commit after a dependency has changed.
-- You don't want to build directly to `latest` in case there are multiple builds
+- You don't want to build directly to `latest` in case there are multiple jobs
happening simultaneously.
[docker-in-docker]: https://blog.docker.com/2013/09/docker-can-now-run-within-docker/
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index aba77490915..00787323b6b 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -8,7 +8,7 @@ run applications in independent "containers" that are run within a single Linux
instance. [Docker Hub][hub] has a rich database of pre-built images that can be
used to test and build your applications.
-Docker, when used with GitLab CI, runs each build in a separate and isolated
+Docker, when used with GitLab CI, runs each job in a separate and isolated
container using the predefined image that is set up in
[`.gitlab-ci.yml`](../yaml/README.md).
@@ -39,18 +39,20 @@ accessible during the build process.
## What is an image
-The `image` keyword is the name of the docker image that is present in the
-local Docker Engine (list all images with `docker images`) or any image that
-can be found at [Docker Hub][hub]. For more information about images and Docker
-Hub please read the [Docker Fundamentals][] documentation.
+The `image` keyword is the name of the docker image the docker executor
+will run to perform the CI tasks.
-In short, with `image` we refer to the docker image, which will be used to
-create a container on which your build will run.
+By default the executor will only pull images from [Docker Hub][hub],
+but this can be configured in the `gitlab-runner/config.toml` by setting
+the [docker pull policy][] to allow using local images.
+
+For more information about images and Docker Hub please read
+the [Docker Fundamentals][] documentation.
## What is a service
The `services` keyword defines just another docker image that is run during
-your build and is linked to the docker image that the `image` keyword defines.
+your job and is linked to the docker image that the `image` keyword defines.
This allows you to access the service image during build time.
The service image can run any application, but the most common use case is to
@@ -61,13 +63,13 @@ time the project is built.
You can see some widely used services examples in the relevant documentation of
[CI services examples](../services/README.md).
-### How services are linked to the build
+### How services are linked to the job
To better understand how the container linking works, read
[Linking containers together][linking-containers].
To summarize, if you add `mysql` as service to your application, the image will
-then be used to create a container that is linked to the build container.
+then be used to create a container that is linked to the job container.
The service container for MySQL will be accessible under the hostname `mysql`.
So, in order to access your database service you have to connect to the host
@@ -133,7 +135,7 @@ Look for the `[runners.docker]` section:
services = ["mysql:latest", "postgres:latest"]
```
-The image and services defined this way will be added to all builds run by
+The image and services defined this way will be added to all job run by
that runner.
## Define an image from a private Docker registry
@@ -167,7 +169,7 @@ services:
- tutum/wordpress:latest
```
-When the build is run, `tutum/wordpress` will be started and you will have
+When the job is run, `tutum/wordpress` will be started and you will have
access to it from your build container under the hostname `tutum__wordpress`.
The alias hostname for the service is made from the image name following these
@@ -202,21 +204,21 @@ See the specific documentation for
## How Docker integration works
-Below is a high level overview of the steps performed by docker during build
+Below is a high level overview of the steps performed by docker during job
time.
1. Create any service container: `mysql`, `postgresql`, `mongodb`, `redis`.
1. Create cache container to store all volumes as defined in `config.toml` and
`Dockerfile` of build image (`ruby:2.1` as in above example).
1. Create build container and link any service container to build container.
-1. Start build container and send build script to the container.
-1. Run build script.
+1. Start build container and send job script to the container.
+1. Run job script.
1. Checkout code in: `/builds/group-name/project-name/`.
1. Run any step defined in `.gitlab-ci.yml`.
1. Check exit status of build script.
1. Remove build container and all created service containers.
-## How to debug a build locally
+## How to debug a job locally
*Note: The following commands are run without root privileges. You should be
able to run docker with your regular user account.*
@@ -271,6 +273,7 @@ containers as well as all volumes (`-v`) that were created with the container
creation.
[Docker Fundamentals]: https://docs.docker.com/engine/understanding-docker/
+[docker pull policy]: https://docs.gitlab.com/runner/executors/docker.html#how-pull-policies-work
[hub]: https://hub.docker.com/
[linking-containers]: https://docs.docker.com/engine/userguide/networking/default_network/dockerlinks/
[tutum/wordpress]: https://hub.docker.com/r/tutum/wordpress/
diff --git a/doc/ci/enable_or_disable_ci.md b/doc/ci/enable_or_disable_ci.md
index c10f82054e2..796a025b951 100644
--- a/doc/ci/enable_or_disable_ci.md
+++ b/doc/ci/enable_or_disable_ci.md
@@ -11,10 +11,10 @@ API.
---
-As of GitLab 8.2, GitLab CI is mainly exposed via the `/builds` page of a
-project. Disabling GitLab CI in a project does not delete any previous builds.
-In fact, the `/builds` page can still be accessed, although it's hidden from
-the left sidebar menu.
+GitLab CI is exposed via the `/pipelines` and `/builds` pages of a project.
+Disabling GitLab CI in a project does not delete any previous jobs.
+In fact, the `/pipelines` and `/builds` pages can still be accessed, although
+it's hidden from the left sidebar menu.
GitLab CI is enabled by default on new installations and can be disabled either
individually under each project's settings, or site-wide by modifying the
@@ -23,12 +23,12 @@ respectively.
### Per-project user setting
-The setting to enable or disable GitLab CI can be found with the name **Builds**
-under the **Features** area of a project's settings along with **Issues**,
-**Merge Requests**, **Wiki** and **Snippets**. Select or deselect the checkbox
-and hit **Save** for the settings to take effect.
+The setting to enable or disable GitLab CI can be found with the name **Pipelines**
+under the **Sharing & Permissions** area of a project's settings along with
+**Merge Requests**. Choose one of **Disabled**, **Only team members** and
+**Everyone with access** and hit **Save changes** for the settings to take effect.
-![Features settings](img/features_settings.png)
+![Sharing & Permissions settings](img/permissions_settings.png)
---
diff --git a/doc/ci/environments.md b/doc/ci/environments.md
index cb62ed723f0..3c31ba45d3d 100644
--- a/doc/ci/environments.md
+++ b/doc/ci/environments.md
@@ -75,7 +75,7 @@ We have defined 3 [stages](yaml/README.md#stages):
- deploy
The jobs assigned to these stages will run in this order. If a job fails, then
-the builds that are assigned to the next stage won't run, rendering the pipeline
+the jobs that are assigned to the next stage won't run, rendering the pipeline
as failed. In our case, the `test` job will run first, then the `build` and
lastly the `deploy_staging`. With this, we ensure that first the tests pass,
then our app is able to be built successfully, and lastly we deploy to the
@@ -119,7 +119,7 @@ There's a bunch of information there, specifically you can see:
- The environment's name with a link to its deployments
- The last deployment ID number and who performed it
-- The build ID of the last deployment with its respective job name
+- The job ID of the last deployment with its respective job name
- The commit information of the last deployment such as who committed, to what
branch and the Git SHA of the commit
- The exact time the last deployment was performed
@@ -219,9 +219,9 @@ deploy_prod:
The `when: manual` action exposes a play button in GitLab's UI and the
`deploy_prod` job will only be triggered if and when we click that play button.
-You can find it in the pipeline, build, environment, and deployment views.
+You can find it in the pipeline, job, environment, and deployment views.
-| Pipelines | Single pipeline | Environments | Deployments | Builds |
+| Pipelines | Single pipeline | Environments | Deployments | jobs |
| --------- | ----------------| ------------ | ----------- | -------|
| ![Pipelines manual action](img/environments_manual_action_pipelines.png) | ![Pipelines manual action](img/environments_manual_action_single_pipeline.png) | ![Environments manual action](img/environments_manual_action_environments.png) | ![Deployments manual action](img/environments_manual_action_deployments.png) | ![Builds manual action](img/environments_manual_action_builds.png) |
@@ -419,7 +419,7 @@ Behind the scenes:
- GitLab Runner picks up the changes and starts running the jobs
- The jobs run sequentially as defined in `stages`
- First, the tests pass
- - Then, the build begins and successfully also passes
+ - Then, the job begins and successfully also passes
- Lastly, the app is deployed to an environment with a name specific to the
branch
@@ -535,6 +535,7 @@ deploy_review:
- master
stop_review:
+ stage: deploy
variables:
GIT_STRATEGY: none
script:
@@ -555,7 +556,9 @@ when their associated branch is deleted.
When you have an environment that has a stop action defined (typically when
the environment describes a review app), GitLab will automatically trigger a
-stop action when the associated branch is deleted.
+stop action when the associated branch is deleted. The `stop_review` job must
+be in the same `stage` as the `deploy_review` one in order for the environment
+to automatically stop.
You can read more in the [`.gitlab-ci.yml` reference][onstop].
diff --git a/doc/ci/examples/deployment/README.md b/doc/ci/examples/deployment/README.md
index 7d91ce6710f..d28aa282825 100644
--- a/doc/ci/examples/deployment/README.md
+++ b/doc/ci/examples/deployment/README.md
@@ -91,7 +91,7 @@ Secure Variables can added by going to `Project > Variables > Add Variable`.
**This feature requires `gitlab-runner` with version equal or greater than 0.4.0.**
The variables that are defined in the project settings are sent along with the build script to the runner.
The secure variables are stored out of the repository. Never store secrets in your projects' .gitlab-ci.yml.
-It is also important that secret's value is hidden in the build log.
+It is also important that secret's value is hidden in the job log.
You access added variable by prefixing it's name with `$` (on non-Windows runners) or `%` (for Windows Batch runners):
1. `$SECRET_VARIABLE` - use it for non-Windows runners
diff --git a/doc/ci/examples/deployment/composer-npm-deploy.md b/doc/ci/examples/deployment/composer-npm-deploy.md
index 5334a73e1f5..8b0d8a003fd 100644
--- a/doc/ci/examples/deployment/composer-npm-deploy.md
+++ b/doc/ci/examples/deployment/composer-npm-deploy.md
@@ -65,7 +65,7 @@ In order, this means that:
1. We check if the `ssh-agent` is available and we install it if it's not;
2. We create the `~/.ssh` folder;
3. We make sure we're running bash;
-4. We disable host checking (we don't ask for user accept when we first connect to a server; and since every build will equal a first connect, we kind of need this)
+4. We disable host checking (we don't ask for user accept when we first connect to a server; and since every job will equal a first connect, we kind of need this)
And this is basically all you need in the `before_script` section.
@@ -153,4 +153,4 @@ stage_deploy:
- scp -P22 -r build/* server_user@server_host:htdocs/wp-content/themes/_tmp
- ssh -p22 server_user@server_host "mv htdocs/wp-content/themes/live htdocs/wp-content/themes/_old && mv htdocs/wp-content/themes/_tmp htdocs/wp-content/themes/live"
- ssh -p22 server_user@server_host "rm -rf htdocs/wp-content/themes/_old"
-``` \ No newline at end of file
+```
diff --git a/doc/ci/examples/php.md b/doc/ci/examples/php.md
index 5eeec92d976..f2dd12b67d3 100644
--- a/doc/ci/examples/php.md
+++ b/doc/ci/examples/php.md
@@ -15,10 +15,10 @@ This will allow us to test PHP projects against different versions of PHP.
However, not everything is plug 'n' play, you still need to configure some
things manually.
-As with every build, you need to create a valid `.gitlab-ci.yml` describing the
+As with every job, you need to create a valid `.gitlab-ci.yml` describing the
build environment.
-Let's first specify the PHP image that will be used for the build process
+Let's first specify the PHP image that will be used for the job process
(you can read more about what an image means in the Runner's lingo reading
about [Using Docker images](../docker/using_docker_images.md#what-is-image)).
@@ -58,8 +58,8 @@ docker-php-ext-install pdo_mysql
```
You might wonder what `docker-php-ext-install` is. In short, it is a script
-provided by the official php docker image that you can use to easilly install
-extensions. For more information read the the documentation at
+provided by the official php docker image that you can use to easily install
+extensions. For more information read the documentation at
<https://hub.docker.com/r/_/php/>.
Now that we created the script that contains all prerequisites for our build
@@ -142,7 +142,7 @@ Of course, `my_php.ini` must be present in the root directory of your repository
## Test PHP projects using the Shell executor
-The shell executor runs your builds in a terminal session on your server.
+The shell executor runs your job in a terminal session on your server.
Thus, in order to test your projects you first need to make sure that all
dependencies are installed.
@@ -280,7 +280,7 @@ that runs on [GitLab.com](https://gitlab.com) using our publicly available
[shared runners](../runners/README.md).
Want to hack on it? Simply fork it, commit and push your changes. Within a few
-moments the changes will be picked by a public runner and the build will begin.
+moments the changes will be picked by a public runner and the job will begin.
[php-hub]: https://hub.docker.com/r/_/php/
[phpenv]: https://github.com/phpenv/phpenv
diff --git a/doc/ci/examples/test-scala-application.md b/doc/ci/examples/test-scala-application.md
index 85f8849fa99..01c13941c21 100644
--- a/doc/ci/examples/test-scala-application.md
+++ b/doc/ci/examples/test-scala-application.md
@@ -51,14 +51,14 @@ The `deploy` stage automatically deploys the project to Heroku using dpl.
You can use other versions of Scala and SBT by defining them in
`build.sbt`.
-## Display test coverage in build
+## Display test coverage in job
Add the `Coverage was \[\d+.\d+\%\]` regular expression in the
-**Settings ➔ Edit Project ➔ Test coverage parsing** project setting to
+**Settings ➔ CI/CD Pipelines ➔ Coverage report** project setting to
retrieve the [test coverage] rate from the build trace and have it
-displayed with your builds.
+displayed with your jobs.
-**Builds** must be enabled for this option to appear.
+**Pipelines** must be enabled for this option to appear.
## Heroku application
diff --git a/doc/ci/git_submodules.md b/doc/ci/git_submodules.md
index 869743ce80a..36c6e153d95 100644
--- a/doc/ci/git_submodules.md
+++ b/doc/ci/git_submodules.md
@@ -1,14 +1,14 @@
# Using Git submodules with GitLab CI
> **Notes:**
-- GitLab 8.12 introduced a new [CI build permissions model][newperms] and you
+- GitLab 8.12 introduced a new [CI job permissions model][newperms] and you
are encouraged to upgrade your GitLab instance if you haven't done already.
If you are **not** using GitLab 8.12 or higher, you would need to work your way
around submodules in order to access the sources of e.g., `gitlab.com/group/project`
with the use of [SSH keys](ssh_keys/README.md).
-- With GitLab 8.12 onward, your permissions are used to evaluate what a CI build
+- With GitLab 8.12 onward, your permissions are used to evaluate what a CI job
can access. More information about how this system works can be found in the
- [Build permissions model](../user/permissions.md#builds-permissions).
+ [Jobs permissions model](../user/permissions.md#jobs-permissions).
- The HTTP(S) Git protocol [must be enabled][gitpro] in your GitLab instance.
## Configuring the `.gitmodules` file
@@ -27,7 +27,7 @@ Let's consider the following example:
If you are using GitLab 8.12+ and your submodule is on the same GitLab server,
you must update your `.gitmodules` file to use **relative URLs**.
Since Git allows the usage of relative URLs for your `.gitmodules` configuration,
-this easily allows you to use HTTP(S) for cloning all your CI builds and SSH
+this easily allows you to use HTTP(S) for cloning all your CI jobs and SSH
for all your local checkouts. The `.gitmodules` would look like:
```ini
@@ -38,7 +38,7 @@ for all your local checkouts. The `.gitmodules` would look like:
The above configuration will instruct Git to automatically deduce the URL that
should be used when cloning sources. Whether you use HTTP(S) or SSH, Git will use
-that same channel and it will allow to make all your CI builds use HTTP(S)
+that same channel and it will allow to make all your CI jobs use HTTP(S)
(because GitLab CI only uses HTTP(S) for cloning your sources), and all your local
clones will continue using SSH.
@@ -57,13 +57,13 @@ Once `.gitmodules` is correctly configured, you can move on to
## Using Git submodules in your CI jobs
There are a few steps you need to take in order to make submodules work
-correctly with your CI builds:
+correctly with your CI jobs:
1. First, make sure you have used [relative URLs](#configuring-the-gitmodules-file)
for the submodules located in the same GitLab server.
1. Next, if you are using `gitlab-ci-multi-runner` v1.10+, you can set the
`GIT_SUBMODULE_STRATEGY` variable to either `normal` or `recursive` to tell
- the runner to fetch your submodules before the build:
+ the runner to fetch your submodules before the job:
```yaml
variables:
GIT_SUBMODULE_STRATEGY: recursive
@@ -87,9 +87,9 @@ The rationale to set the `sync` and `update` in `before_script` is because of
the way Git submodules work. On a fresh Runner workspace, Git will set the
submodule URL including the token in `.git/config`
(or `.git/modules/<submodule>/config`) based on `.gitmodules` and the current
-remote URL. On subsequent builds on the same Runner, `.git/config` is cached
+remote URL. On subsequent jobs on the same Runner, `.git/config` is cached
and already contains a full URL for the submodule, corresponding to the previous
-build, and to **a token from a previous build**. `sync` allows to force updating
+job, and to **a token from a previous job**. `sync` allows to force updating
the full URL.
[gitpro]: ../user/admin_area/settings/visibility_and_access_controls.md#enabled-git-access-protocols
diff --git a/doc/ci/img/features_settings.png b/doc/ci/img/features_settings.png
deleted file mode 100644
index c159253d1c9..00000000000
--- a/doc/ci/img/features_settings.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/img/permissions_settings.png b/doc/ci/img/permissions_settings.png
new file mode 100644
index 00000000000..1454c75fd24
--- /dev/null
+++ b/doc/ci/img/permissions_settings.png
Binary files differ
diff --git a/doc/ci/img/pipelines-goal.svg b/doc/ci/img/pipelines-goal.svg
new file mode 100644
index 00000000000..a925e2282a4
--- /dev/null
+++ b/doc/ci/img/pipelines-goal.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" standalone="yes"?>
+
+<svg version="1.1" viewBox="0.0 0.0 1091.020997375328 262.04461942257217" fill="none" stroke="none" stroke-linecap="square" stroke-miterlimit="10" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="p.0"><path d="m0 0l1091.021 0l0 262.04462l-1091.021 0l0 -262.04462z" clip-rule="nonzero"></path></clipPath><g clip-path="url(#p.0)"><path fill="#000000" fill-opacity="0.0" d="m0 0l1091.021 0l0 262.04462l-1091.021 0z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m226.93439 3.7664042l860.7559 0l0 249.5748l-860.7559 0z" fill-rule="nonzero"></path><path stroke="#666666" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" stroke-dasharray="4.0,3.0" d="m226.93439 3.7664042l860.7559 0l0 249.5748l-860.7559 0z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m67.72179 27.199474l147.2126 0l0 39.464565l-147.2126 0z" fill-rule="nonzero"></path><path fill="#000000" d="m126.91313 49.353848l1.796875 0.453125q-0.5625 2.21875 -2.03125 3.390625q-1.46875 1.15625 -3.59375 1.15625q-2.203125 0 -3.578125 -0.890625q-1.375 -0.90625 -2.09375 -2.59375q-0.71875 -1.703125 -0.71875 -3.65625q0 -2.125 0.796875 -3.703125q0.8125 -1.578125 2.3125 -2.390625q1.5 -0.828125 3.296875 -0.828125q2.046875 0 3.4375 1.046875q1.390625 1.03125 1.9375 2.90625l-1.765625 0.421875q-0.46875 -1.484375 -1.375 -2.15625q-0.90625 -0.6875 -2.265625 -0.6875q-1.5625 0 -2.625 0.75q-1.046875 0.75 -1.484375 2.03125q-0.421875 1.265625 -0.421875 2.609375q0 1.734375 0.5 3.03125q0.515625 1.28125 1.578125 1.921875q1.078125 0.640625 2.3125 0.640625q1.515625 0 2.5625 -0.859375q1.046875 -0.875 1.421875 -2.59375zm3.5354462 4.765625l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0zm12.978302 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm15.547592 4.65625q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.03125q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.328125 0.09375 2.953125q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.71875q-0.90625 0.359375 -2.734375 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.671875 0.5 1.125q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.15625q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm7.735092 3.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm8.277054 -1.671875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm14.449646 5.875l0 -13.59375l2.71875 0l3.21875 9.625q0.4375 1.34375 0.640625 2.015625q0.234375 -0.75 0.734375 -2.1875l3.25 -9.453125l2.421875 0l0 13.59375l-1.734375 0l0 -11.390625l-3.953125 11.390625l-1.625 0l-3.9375 -11.578125l0 11.578125l-1.734375 0zm15.634552 0l0 -13.59375l6.03125 0q1.8125 0 2.75 0.359375q0.953125 0.359375 1.515625 1.296875q0.5625 0.921875 0.5625 2.046875q0 1.453125 -0.9375 2.453125q-0.921875 0.984375 -2.890625 1.25q0.71875 0.34375 1.09375 0.671875q0.78125 0.734375 1.484375 1.8125l2.375 3.703125l-2.265625 0l-1.796875 -2.828125q-0.796875 -1.21875 -1.3125 -1.875q-0.5 -0.65625 -0.90625 -0.90625q-0.40625 -0.265625 -0.8125 -0.359375q-0.3125 -0.078125 -1.015625 -0.078125l-2.078125 0l0 6.046875l-1.796875 0zm1.796875 -7.59375l3.859375 0q1.234375 0 1.921875 -0.25q0.703125 -0.265625 1.0625 -0.828125q0.375 -0.5625 0.375 -1.21875q0 -0.96875 -0.703125 -1.578125q-0.703125 -0.625 -2.21875 -0.625l-4.296875 0l0 4.5z" fill-rule="nonzero"></path><path fill="#efefef" d="m765.3307 106.94125l147.21265 0l0 59.74803l-147.21265 0z" fill-rule="nonzero"></path><path stroke="#999999" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m765.3307 106.94125l147.21265 0l0 59.74803l-147.21265 0z" fill-rule="nonzero"></path><path fill="#000000" d="m800.99805 132.73526l0 -13.593742l4.6875 0q1.578125 0 2.421875 0.1875q1.15625 0.265625 1.984375 0.96875q1.078125 0.921875 1.609375 2.34375q0.53125 1.40625 0.53125 3.21875q0 1.546875 -0.359375 2.7499924q-0.359375 1.1875 -0.921875 1.984375q-0.5625 0.78125 -1.234375 1.234375q-0.671875 0.4375 -1.625 0.671875q-0.953125 0.234375 -2.1875 0.234375l-4.90625 0zm1.796875 -1.609375l2.90625 0q1.34375 0 2.109375 -0.25q0.765625 -0.25 1.21875 -0.703125q0.640625 -0.640625 1.0 -1.71875q0.359375 -1.0781174 0.359375 -2.6249924q0 -2.125 -0.703125 -3.265625q-0.703125 -1.15625 -1.703125 -1.546875q-0.71875 -0.28125 -2.328125 -0.28125l-2.859375 0l0 10.390617zm18.207336 -1.5625l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.7343674q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.43749237l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.7031174l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm9.110107 9.656242l0 -13.640617l1.53125 0l0 1.28125q0.53125 -0.75 1.203125 -1.125q0.6875 -0.375 1.640625 -0.375q1.265625 0 2.234375 0.65625q0.96875 0.640625 1.453125 1.828125q0.5 1.1875 0.5 2.59375q0 1.5156174 -0.546875 2.7343674q-0.546875 1.203125 -1.578125 1.84375q-1.03125 0.640625 -2.171875 0.640625q-0.84375 0 -1.515625 -0.34375q-0.65625 -0.359375 -1.078125 -0.890625l0 4.796875l-1.671875 0zm1.515625 -8.656242q0 1.9062424 0.765625 2.8124924q0.78125 0.90625 1.875 0.90625q1.109375 0 1.890625 -0.9375q0.796875 -0.9375 0.796875 -2.9218674q0 -1.875 -0.78125 -2.8125q-0.765625 -0.9375 -1.84375 -0.9375q-1.0625 0 -1.890625 1.0q-0.8125 1.0 -0.8125 2.890625zm8.828857 4.8749924l0 -13.593742l1.671875 0l0 13.593742l-1.671875 0zm3.5510254 -4.9218674q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.9062424 -0.578125 2.9999924q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8124924zm1.71875 0q0 1.8906174 0.828125 2.8281174q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.8906174q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.203857 8.718742l-0.171875 -1.5625q0.546875 0.140625 0.953125 0.140625q0.546875 0 0.875 -0.1875q0.34375 -0.1875 0.5625 -0.515625q0.15625 -0.25 0.5 -1.25q0.046875 -0.140625 0.15625 -0.40625l-3.734375 -9.874992l1.796875 0l2.046875 5.7187424q0.40625 1.078125 0.71875 2.28125q0.28125 -1.15625 0.6875 -2.25l2.09375 -5.7499924l1.671875 0l-3.75 10.031242q-0.59375 1.625 -0.9375 2.234375q-0.4375 0.828125 -1.015625 1.203125q-0.578125 0.390625 -1.375 0.390625q-0.484375 0 -1.078125 -0.203125zm18.245789 -5.296875l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.6562424l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.7499924q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm0.9020996 -3.4218674q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.9062424 -0.578125 2.9999924q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8124924zm1.71875 0q0 1.8906174 0.828125 2.8281174q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.8906174q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125z" fill-rule="nonzero"></path><path fill="#000000" d="m808.1591 150.36026l1.6875 -0.140625q0.125 1.015625 0.5625 1.671875q0.4375 0.65625 1.359375 1.0625q0.9375 0.40625 2.09375 0.40625q1.03125 0 1.8125 -0.3125q0.796875 -0.3125 1.1875 -0.84375q0.390625 -0.53125 0.390625 -1.15625q0 -0.640625 -0.375 -1.109375q-0.375 -0.484375 -1.234375 -0.8125q-0.546875 -0.21875 -2.421875 -0.65625q-1.875 -0.453125 -2.625 -0.859375q-0.96875 -0.515625 -1.453125 -1.265625q-0.46875 -0.75 -0.46875 -1.6875q0 -1.03125 0.578125 -1.921875q0.59375 -0.90625 1.703125 -1.359375q1.125 -0.46875 2.5 -0.46875q1.515625 0 2.671875 0.484375q1.15625 0.484375 1.765625 1.4375q0.625 0.9375 0.671875 2.140625l-1.71875 0.125q-0.140625 -1.28125 -0.953125 -1.9375q-0.796875 -0.671875 -2.359375 -0.671875q-1.625 0 -2.375 0.609375q-0.75 0.59375 -0.75 1.4375q0 0.734375 0.53125 1.203125q0.515625 0.46875 2.703125 0.96875q2.203125 0.5 3.015625 0.875q1.1875 0.546875 1.75 1.390625q0.578125 0.828125 0.578125 1.921875q0 1.09375 -0.625 2.0625q-0.625 0.953125 -1.796875 1.484375q-1.15625 0.53125 -2.609375 0.53125q-1.84375 0 -3.09375 -0.53125q-1.25 -0.546875 -1.96875 -1.625q-0.703125 -1.078125 -0.734375 -2.453125zm16.490417 2.875l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm7.9645386 0.28125q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.03125q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.328125 0.09375 2.953125q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.71875q-0.90625 0.359375 -2.734375 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.671875 0.5 1.125q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.15625q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm3.7819824 5.75l1.609375 0.25q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.28125q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.46875 -1.109375 -3.515625q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.515625q0 2.3125 -0.46875 3.265625q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.390625zm1.375 -5.921875q0 1.953125 0.765625 2.84375q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.890625 0.78125 -2.78125q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875zm9.313232 -6.578125l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm4.1292114 0l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0zm10.078796 0.8125l1.609375 0.25q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.28125q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.46875 -1.109375 -3.515625q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.515625q0 2.3125 -0.46875 3.265625q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.390625zm1.375 -5.921875q0 1.953125 0.765625 2.84375q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.890625 0.78125 -2.78125q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875z" fill-rule="nonzero"></path><path fill="#efefef" d="m925.54333 177.39108l147.21252 0l0 59.74803l-147.21252 0z" fill-rule="nonzero"></path><path stroke="#999999" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m925.54333 177.39108l147.21252 0l0 59.74803l-147.21252 0z" fill-rule="nonzero"></path><path fill="#000000" d="m961.2107 203.18509l0 -13.59375l4.6875 0q1.578125 0 2.421875 0.1875q1.15625 0.265625 1.984375 0.96875q1.078125 0.921875 1.609375 2.34375q0.53125 1.40625 0.53125 3.21875q0 1.546875 -0.359375 2.75q-0.359375 1.1875 -0.921875 1.984375q-0.5625 0.78125 -1.234375 1.234375q-0.671875 0.4375 -1.625 0.671875q-0.953125 0.234375 -2.1875 0.234375l-4.90625 0zm1.796875 -1.609375l2.90625 0q1.34375 0 2.109375 -0.25q0.765625 -0.25 1.21875 -0.703125q0.640625 -0.640625 1.0 -1.71875q0.359375 -1.078125 0.359375 -2.625q0 -2.125 -0.703125 -3.265625q-0.703125 -1.15625 -1.703125 -1.546875q-0.71875 -0.28125 -2.328125 -0.28125l-2.859375 0l0 10.390625zm18.207275 -1.5625l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm9.110107 9.65625l0 -13.640625l1.53125 0l0 1.28125q0.53125 -0.75 1.203125 -1.125q0.6875 -0.375 1.640625 -0.375q1.265625 0 2.234375 0.65625q0.96875 0.640625 1.453125 1.828125q0.5 1.1875 0.5 2.59375q0 1.515625 -0.546875 2.734375q-0.546875 1.203125 -1.578125 1.84375q-1.03125 0.640625 -2.171875 0.640625q-0.84375 0 -1.515625 -0.34375q-0.65625 -0.359375 -1.078125 -0.890625l0 4.796875l-1.671875 0zm1.515625 -8.65625q0 1.90625 0.765625 2.8125q0.78125 0.90625 1.875 0.90625q1.109375 0 1.890625 -0.9375q0.796875 -0.9375 0.796875 -2.921875q0 -1.875 -0.78125 -2.8125q-0.765625 -0.9375 -1.84375 -0.9375q-1.0625 0 -1.890625 1.0q-0.8125 1.0 -0.8125 2.890625zm8.828857 4.875l0 -13.59375l1.671875 0l0 13.59375l-1.671875 0zm3.5510864 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.203796 8.71875l-0.171875 -1.5625q0.546875 0.140625 0.953125 0.140625q0.546875 0 0.875 -0.1875q0.34375 -0.1875 0.5625 -0.515625q0.15625 -0.25 0.5 -1.25q0.046875 -0.140625 0.15625 -0.40625l-3.734375 -9.875l1.796875 0l2.046875 5.71875q0.40625 1.078125 0.71875 2.28125q0.28125 -1.15625 0.6875 -2.25l2.09375 -5.75l1.671875 0l-3.75 10.03125q-0.59375 1.625 -0.9375 2.234375q-0.4375 0.828125 -1.015625 1.203125q-0.578125 0.390625 -1.375 0.390625q-0.484375 0 -1.078125 -0.203125zm18.24585 -5.296875l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm0.90197754 -3.421875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125z" fill-rule="nonzero"></path><path fill="#000000" d="m956.0228 225.18509l0 -13.59375l5.125 0q1.359375 0 2.078125 0.125q1.0 0.171875 1.671875 0.640625q0.671875 0.46875 1.078125 1.3125q0.421875 0.84375 0.421875 1.84375q0 1.734375 -1.109375 2.9375q-1.09375 1.203125 -3.984375 1.203125l-3.484375 0l0 5.53125l-1.796875 0zm1.796875 -7.140625l3.515625 0q1.75 0 2.46875 -0.640625q0.734375 -0.65625 0.734375 -1.828125q0 -0.859375 -0.4375 -1.46875q-0.421875 -0.609375 -1.125 -0.796875q-0.453125 -0.125 -1.671875 -0.125l-3.484375 0l0 4.859375zm10.4122925 7.140625l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0zm5.6033325 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm15.672607 4.921875l0 -1.25q-0.9375 1.46875 -2.75 1.46875q-1.171875 0 -2.171875 -0.640625q-0.984375 -0.65625 -1.53125 -1.8125q-0.53125 -1.171875 -0.53125 -2.6875q0 -1.46875 0.484375 -2.671875q0.5 -1.203125 1.46875 -1.84375q0.984375 -0.640625 2.203125 -0.640625q0.890625 0 1.578125 0.375q0.703125 0.375 1.140625 0.984375l0 -4.875l1.65625 0l0 13.59375l-1.546875 0zm-5.28125 -4.921875q0 1.890625 0.796875 2.828125q0.8125 0.9375 1.890625 0.9375q1.09375 0 1.859375 -0.890625q0.765625 -0.890625 0.765625 -2.734375q0 -2.015625 -0.78125 -2.953125q-0.78125 -0.953125 -1.921875 -0.953125q-1.109375 0 -1.859375 0.90625q-0.75 0.90625 -0.75 2.859375zm15.719421 4.921875l0 -1.453125q-1.140625 1.671875 -3.125 1.671875q-0.859375 0 -1.625 -0.328125q-0.75 -0.34375 -1.125 -0.84375q-0.359375 -0.5 -0.515625 -1.234375q-0.09375 -0.5 -0.09375 -1.5625l0 -6.109375l1.671875 0l0 5.46875q0 1.3125 0.09375 1.765625q0.15625 0.65625 0.671875 1.03125q0.515625 0.375 1.265625 0.375q0.75 0 1.40625 -0.375q0.65625 -0.390625 0.921875 -1.046875q0.28125 -0.671875 0.28125 -1.9375l0 -5.28125l1.671875 0l0 9.859375l-1.5 0zm10.360107 -3.609375l1.640625 0.21875q-0.265625 1.6875 -1.375 2.65625q-1.109375 0.953125 -2.734375 0.953125q-2.015625 0 -3.25 -1.3125q-1.21875 -1.328125 -1.21875 -3.796875q0 -1.59375 0.515625 -2.78125q0.53125 -1.203125 1.609375 -1.796875q1.09375 -0.609375 2.359375 -0.609375q1.609375 0 2.625 0.8125q1.015625 0.8125 1.3125 2.3125l-1.625 0.25q-0.234375 -1.0 -0.828125 -1.5q-0.59375 -0.5 -1.421875 -0.5q-1.265625 0 -2.0625 0.90625q-0.78125 0.90625 -0.78125 2.859375q0 1.984375 0.765625 2.890625q0.765625 0.890625 1.984375 0.890625q0.984375 0 1.640625 -0.59375q0.65625 -0.609375 0.84375 -1.859375zm6.546875 2.109375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm1.5426636 -10.1875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm3.5042114 -4.921875q0 -2.734375 1.531311 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.281311 -1.328125 -1.281311 -3.8125zm1.718811 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.28186 4.921875l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m57.989502 117.082985l156.94489 0l0 39.46456l-156.94489 0z" fill-rule="nonzero"></path><path fill="#000000" d="m71.518036 144.00298l0 -13.59375l2.71875 0l3.21875 9.625q0.4375 1.34375 0.640625 2.015625q0.234375 -0.75 0.734375 -2.1875l3.25 -9.453125l2.421875 0l0 13.59375l-1.734375 0l0 -11.390625l-3.953125 11.390625l-1.625 0l-3.9375 -11.578125l0 11.578125l-1.734375 0zm22.134552 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm9.094467 5.875l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0zm5.931427 0.8125l1.609375 0.25q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.28125q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.46875 -1.109375 -3.515625q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.515625q0 2.3125 -0.46875 3.265625q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.390625zm1.375 -5.921875q0 1.953125 0.765625 2.84375q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.890625 0.78125 -2.78125q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875zm16.047592 1.9375l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm17.949646 4.375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm0.90205383 -3.421875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm14.621521 4.921875l0 -13.59375l2.71875 0l3.21875 9.625q0.4375 1.34375 0.640625 2.015625q0.234375 -0.75 0.734375 -2.1875l3.25 -9.453125l2.421875 0l0 13.59375l-1.734375 0l0 -11.390625l-3.953125 11.390625l-1.625 0l-3.9375 -11.578125l0 11.578125l-1.734375 0zm21.822052 -1.21875q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.03125q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.328125 0.09375 2.953125q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.71875q-0.90625 0.359375 -2.734375 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.671875 0.5 1.125q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.15625q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm3.4069672 2.0l1.65625 -0.265625q0.140625 1.0 0.765625 1.53125q0.640625 0.515625 1.78125 0.515625q1.15625 0 1.703125 -0.46875q0.5625 -0.46875 0.5625 -1.09375q0 -0.5625 -0.484375 -0.890625q-0.34375 -0.21875 -1.703125 -0.5625q-1.84375 -0.46875 -2.5625 -0.796875q-0.703125 -0.34375 -1.078125 -0.9375q-0.359375 -0.609375 -0.359375 -1.328125q0 -0.65625 0.296875 -1.21875q0.3125 -0.5625 0.828125 -0.9375q0.390625 -0.28125 1.0625 -0.484375q0.671875 -0.203125 1.4375 -0.203125q1.171875 0 2.046875 0.34375q0.875 0.328125 1.28125 0.90625q0.421875 0.5625 0.578125 1.515625l-1.625 0.21875q-0.109375 -0.75 -0.65625 -1.171875q-0.53125 -0.4375 -1.5 -0.4375q-1.15625 0 -1.640625 0.390625q-0.484375 0.375 -0.484375 0.875q0 0.328125 0.203125 0.59375q0.203125 0.265625 0.640625 0.4375q0.25 0.09375 1.46875 0.4375q1.765625 0.46875 2.46875 0.765625q0.703125 0.296875 1.09375 0.875q0.40625 0.578125 0.40625 1.4375q0 0.828125 -0.484375 1.578125q-0.484375 0.734375 -1.40625 1.140625q-0.921875 0.390625 -2.078125 0.390625q-1.921875 0 -2.9375 -0.796875q-1.0 -0.796875 -1.28125 -2.359375zm13.65625 1.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm8.277054 -1.671875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm9.094467 5.875l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m5.0104985 187.5328l209.95276 0l0 39.46457l-209.95276 0z" fill-rule="nonzero"></path><path fill="#000000" d="m34.188858 214.4528l0 -13.59375l2.71875 0l3.21875 9.625q0.4375 1.34375 0.640625 2.015625q0.234375 -0.75 0.734375 -2.1875l3.25 -9.453125l2.421875 0l0 13.59375l-1.734375 0l0 -11.390625l-3.953125 11.390625l-1.625 0l-3.9375 -11.578125l0 11.578125l-1.734375 0zm22.134552 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm9.094467 5.875l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0zm5.931427 0.8125l1.609375 0.25q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.28125q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.46875 -1.109375 -3.515625q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.515625q0 2.3125 -0.46875 3.265625q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.390625zm1.375 -5.921875q0 1.953125 0.765625 2.84375q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.890625 0.78125 -2.78125q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875zm16.047592 1.9375l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm17.949646 4.375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm0.90205383 -3.421875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm14.684021 4.921875l0 -13.59375l5.125 0q1.359375 0 2.078125 0.125q1.0 0.171875 1.671875 0.640625q0.671875 0.46875 1.078125 1.3125q0.421875 0.84375 0.421875 1.84375q0 1.734375 -1.109375 2.9375q-1.09375 1.203125 -3.984375 1.203125l-3.484375 0l0 5.53125l-1.796875 0zm1.796875 -7.140625l3.515625 0q1.75 0 2.46875 -0.640625q0.734375 -0.65625 0.734375 -1.828125q0 -0.859375 -0.4375 -1.46875q-0.421875 -0.609375 -1.125 -0.796875q-0.453125 -0.125 -1.671875 -0.125l-3.484375 0l0 4.859375zm10.412323 7.140625l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0zm5.603302 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm15.672592 4.921875l0 -1.25q-0.9375 1.46875 -2.75 1.46875q-1.171875 0 -2.171875 -0.640625q-0.984375 -0.65625 -1.53125 -1.8125q-0.53125 -1.171875 -0.53125 -2.6875q0 -1.46875 0.484375 -2.671875q0.5 -1.203125 1.46875 -1.84375q0.984375 -0.640625 2.203125 -0.640625q0.890625 0 1.578125 0.375q0.703125 0.375 1.140625 0.984375l0 -4.875l1.65625 0l0 13.59375l-1.546875 0zm-5.28125 -4.921875q0 1.890625 0.796875 2.828125q0.8125 0.9375 1.890625 0.9375q1.09375 0 1.859375 -0.890625q0.765625 -0.890625 0.765625 -2.734375q0 -2.015625 -0.78125 -2.953125q-0.78125 -0.953125 -1.921875 -0.953125q-1.109375 0 -1.859375 0.90625q-0.75 0.90625 -0.75 2.859375zm15.719467 4.921875l0 -1.453125q-1.140625 1.671875 -3.125 1.671875q-0.859375 0 -1.625 -0.328125q-0.75 -0.34375 -1.125 -0.84375q-0.359375 -0.5 -0.515625 -1.234375q-0.09375 -0.5 -0.09375 -1.5625l0 -6.109375l1.671875 0l0 5.46875q0 1.3125 0.09375 1.765625q0.15625 0.65625 0.671875 1.03125q0.515625 0.375 1.265625 0.375q0.75 0 1.40625 -0.375q0.65625 -0.390625 0.921875 -1.046875q0.28125 -0.671875 0.28125 -1.9375l0 -5.28125l1.671875 0l0 9.859375l-1.5 0zm10.360092 -3.609375l1.640625 0.21875q-0.265625 1.6875 -1.375 2.65625q-1.109375 0.953125 -2.734375 0.953125q-2.015625 0 -3.25 -1.3125q-1.21875 -1.328125 -1.21875 -3.796875q0 -1.59375 0.515625 -2.78125q0.53125 -1.203125 1.609375 -1.796875q1.09375 -0.609375 2.359375 -0.609375q1.609375 0 2.625 0.8125q1.015625 0.8125 1.3125 2.3125l-1.625 0.25q-0.234375 -1.0 -0.828125 -1.5q-0.59375 -0.5 -1.421875 -0.5q-1.265625 0 -2.0625 0.90625q-0.78125 0.90625 -0.78125 2.859375q0 1.984375 0.765625 2.890625q0.765625 0.890625 1.984375 0.890625q0.984375 0 1.640625 -0.59375q0.65625 -0.609375 0.84375 -1.859375zm6.546875 2.109375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm1.5426788 -10.1875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm3.5041962 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.281967 4.921875l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0zm10.813217 0l0 -1.90625l1.90625 0l0 1.90625q0 1.046875 -0.375 1.6875q-0.375 0.65625 -1.171875 1.0l-0.46875 -0.71875q0.53125 -0.21875 0.78125 -0.671875q0.25 -0.453125 0.28125 -1.296875l-0.953125 0z" fill-rule="nonzero"></path><path fill="#000000" d="m51.19565 236.4528l0 -12.0l-4.46875 0l0 -1.59375l10.765625 0l0 1.59375l-4.5 0l0 12.0l-1.796875 0zm14.161606 -1.21875q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.03125q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.328125 0.09375 2.953125q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.71875q-0.90625 0.359375 -2.734375 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.671875 0.5 1.125q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.15625q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm3.7819672 5.75l1.609375 0.25q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.28125q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.46875 -1.109375 -3.515625q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.515625q0 2.3125 -0.46875 3.265625q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.390625zm1.375 -5.921875q0 1.953125 0.765625 2.84375q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.890625 0.78125 -2.78125q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875zm14.887146 5.109375l0 -8.546875l-1.484375 0l0 -1.3125l1.484375 0l0 -1.046875q0 -0.984375 0.171875 -1.46875q0.234375 -0.65625 0.84375 -1.046875q0.609375 -0.40625 1.703125 -0.40625q0.703125 0 1.5625 0.15625l-0.25 1.46875q-0.515625 -0.09375 -0.984375 -0.09375q-0.765625 0 -1.078125 0.328125q-0.3125 0.3125 -0.3125 1.203125l0 0.90625l1.921875 0l0 1.3125l-1.921875 0l0 8.546875l-1.65625 0zm4.152054 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.266342 4.921875l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0zm11.661606 0l0 -13.59375l6.03125 0q1.8125 0 2.75 0.359375q0.953125 0.359375 1.515625 1.296875q0.5625 0.921875 0.5625 2.046875q0 1.453125 -0.9375 2.453125q-0.921875 0.984375 -2.890625 1.25q0.71875 0.34375 1.09375 0.671875q0.78125 0.734375 1.484375 1.8125l2.375 3.703125l-2.265625 0l-1.796875 -2.828125q-0.796875 -1.21875 -1.3125 -1.875q-0.5 -0.65625 -0.90625 -0.90625q-0.40625 -0.265625 -0.8125 -0.359375q-0.3125 -0.078125 -1.015625 -0.078125l-2.078125 0l0 6.046875l-1.796875 0zm1.796875 -7.59375l3.859375 0q1.234375 0 1.921875 -0.25q0.703125 -0.265625 1.0625 -0.828125q0.375 -0.5625 0.375 -1.21875q0 -0.96875 -0.703125 -1.578125q-0.703125 -0.625 -2.21875 -0.625l-4.296875 0l0 4.5zm18.176071 4.421875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm9.078842 5.875l0 -13.59375l1.671875 0l0 13.59375l-1.671875 0zm10.926071 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm15.547592 4.65625q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.03125q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.328125 0.09375 2.953125q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.71875q-0.90625 0.359375 -2.734375 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.671875 0.5 1.125q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.15625q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm3.4069672 2.0l1.65625 -0.265625q0.140625 1.0 0.765625 1.53125q0.640625 0.515625 1.78125 0.515625q1.15625 0 1.703125 -0.46875q0.5625 -0.46875 0.5625 -1.09375q0 -0.5625 -0.484375 -0.890625q-0.34375 -0.21875 -1.703125 -0.5625q-1.84375 -0.46875 -2.5625 -0.796875q-0.703125 -0.34375 -1.078125 -0.9375q-0.359375 -0.609375 -0.359375 -1.328125q0 -0.65625 0.296875 -1.21875q0.3125 -0.5625 0.828125 -0.9375q0.390625 -0.28125 1.0625 -0.484375q0.671875 -0.203125 1.4375 -0.203125q1.171875 0 2.046875 0.34375q0.875 0.328125 1.28125 0.90625q0.421875 0.5625 0.578125 1.515625l-1.625 0.21875q-0.109375 -0.75 -0.65625 -1.171875q-0.53125 -0.4375 -1.5 -0.4375q-1.15625 0 -1.640625 0.390625q-0.484375 0.375 -0.484375 0.875q0 0.328125 0.203125 0.59375q0.203125 0.265625 0.640625 0.4375q0.25 0.09375 1.46875 0.4375q1.765625 0.46875 2.46875 0.765625q0.703125 0.296875 1.09375 0.875q0.40625 0.578125 0.40625 1.4375q0 0.828125 -0.484375 1.578125q-0.484375 0.734375 -1.40625 1.140625q-0.921875 0.390625 -2.078125 0.390625q-1.921875 0 -2.9375 -0.796875q-1.0 -0.796875 -1.28125 -2.359375zm16.75 -0.234375l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm9.547592 5.875l0 -1.90625l1.90625 0l0 1.90625q0 1.046875 -0.375 1.6875q-0.375 0.65625 -1.171875 1.0l-0.46875 -0.71875q0.53125 -0.21875 0.78125 -0.671875q0.25 -0.453125 0.28125 -1.296875l-0.953125 0zm9.304108 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.266342 4.921875l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0z" fill-rule="nonzero"></path><path fill="#000000" d="m88.11708 258.45282l0 -13.593765l2.71875 0l3.21875 9.625q0.4375 1.34375 0.640625 2.0156403q0.234375 -0.75001526 0.734375 -2.1875153l3.25 -9.453125l2.421875 0l0 13.593765l-1.734375 0l0 -11.39064l-3.953125 11.39064l-1.625 0l-3.9375 -11.57814l0 11.57814l-1.734375 0zm21.822052 -1.21875q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.0312653q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.3281403 0.09375 2.9531403q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.7187653q-0.90625 0.359375 -2.734375 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.67189026 0.5 1.1250153q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.1562653q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm4.078842 4.9375153l0 -9.85939l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625153l-1.671875 0l0 -6.0000153q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.3750153l-1.671875 0zm16.828842 0l0 -1.453125q-1.140625 1.671875 -3.125 1.671875q-0.859375 0 -1.625 -0.328125q-0.75 -0.34375 -1.125 -0.84375q-0.359375 -0.5 -0.515625 -1.234375q-0.09375 -0.50001526 -0.09375 -1.5625153l0 -6.109375l1.671875 0l0 5.46875q0 1.3125 0.09375 1.765625q0.15625 0.65626526 0.671875 1.0312653q0.515625 0.375 1.265625 0.375q0.75 0 1.40625 -0.375q0.65625 -0.390625 0.921875 -1.0468903q0.28125 -0.671875 0.28125 -1.9375l0 -5.28125l1.671875 0l0 9.85939l-1.5 0zm10.360092 -1.21875q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.0312653q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.3281403 0.09375 2.9531403q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.7187653q-0.90625 0.359375 -2.734375 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.67189026 0.5 1.1250153q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.1562653q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm4.047592 4.9375153l0 -13.593765l1.671875 0l0 13.593765l-1.671875 0zm13.015625 -1.5l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.9843903l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71876526 0.078125 0.92189026q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm1.5114288 1.5l0 -9.85939l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.1562653l-1.671875 0zm6.243927 -11.687515l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.687515l0 -9.85939l1.671875 0l0 9.85939l-1.671875 0zm3.8323212 0.8125l1.609375 0.25q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.28125q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.4687653 -1.109375 -3.5156403q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.51564q0 2.3125 -0.46875 3.265625q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.390625zm1.375 -5.9218903q0 1.953125 0.765625 2.8437653q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.89064026 0.78125 -2.7812653q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875zm9.000717 5.9218903l1.609375 0.25q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.28125q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.4687653 -1.109375 -3.5156403q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.51564q0 2.3125 -0.46875 3.265625q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.390625zm1.375 -5.9218903q0 1.953125 0.765625 2.8437653q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.89064026 0.78125 -2.7812653q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875zm16.047592 1.9375l1.71875 0.21875q-0.40625 1.5000153 -1.515625 2.3437653q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.7343903q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.4843903q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.5468903zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm9.094467 5.8750153l0 -9.85939l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.1562653l-1.671875 0z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m214.93439 46.93176l13.599762 0l0 0.062992096l13.612839 0" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m214.93439 46.93176l13.599747 0l0 0.062992096l7.612854 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m236.14699 48.646484l4.538086 -1.6517334l-4.538086 -1.6517334z" fill-rule="evenodd"></path><path fill="#efefef" d="m242.13387 17.057743l147.2126 0l0 59.748028l-147.2126 0z" fill-rule="nonzero"></path><path stroke="#999999" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m242.13387 17.057743l147.2126 0l0 59.748028l-147.2126 0z" fill-rule="nonzero"></path><path fill="#000000" d="m296.38846 53.851757l0 -13.59375l5.109375 0q1.546875 0 2.484375 0.40625q0.953125 0.40625 1.484375 1.265625q0.53125 0.859375 0.53125 1.796875q0 0.875 -0.46875 1.65625q-0.46875 0.765625 -1.4375 1.234375q1.234375 0.359375 1.890625 1.234375q0.671875 0.875 0.671875 2.0625q0 0.953125 -0.40625 1.78125q-0.390625 0.8125 -0.984375 1.265625q-0.59375 0.4375 -1.5 0.671875q-0.890625 0.21875 -2.1875 0.21875l-5.1875 0zm1.796875 -7.890625l2.9375 0q1.203125 0 1.71875 -0.15625q0.6875 -0.203125 1.03125 -0.671875q0.359375 -0.46875 0.359375 -1.1875q0 -0.671875 -0.328125 -1.1875q-0.328125 -0.515625 -0.9375 -0.703125q-0.59375 -0.203125 -2.0625 -0.203125l-2.71875 0l0 4.109375zm0 6.28125l3.390625 0q0.875 0 1.21875 -0.0625q0.625 -0.109375 1.046875 -0.359375q0.421875 -0.265625 0.6875 -0.765625q0.265625 -0.5 0.265625 -1.140625q0 -0.765625 -0.390625 -1.328125q-0.390625 -0.5625 -1.078125 -0.78125q-0.6875 -0.234375 -1.984375 -0.234375l-3.15625 0l0 4.671875zm16.959198 1.609375l0 -1.453125q-1.140625 1.671875 -3.125 1.671875q-0.859375 0 -1.625 -0.328125q-0.75 -0.34375 -1.125 -0.84375q-0.359375 -0.5 -0.515625 -1.234375q-0.09375 -0.5 -0.09375 -1.5625l0 -6.109375l1.671875 0l0 5.46875q0 1.3125 0.09375 1.765625q0.15625 0.65625 0.671875 1.03125q0.515625 0.375 1.265625 0.375q0.75 0 1.40625 -0.375q0.65625 -0.390625 0.921875 -1.046875q0.28125 -0.671875 0.28125 -1.9375l0 -5.28125l1.671875 0l0 9.859375l-1.5 0zm3.9382324 -11.6875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm4.097931 0l0 -13.59375l1.671875 0l0 13.59375l-1.671875 0zm10.566711 0l0 -1.25q-0.9375 1.46875 -2.75 1.46875q-1.171875 0 -2.171875 -0.640625q-0.984375 -0.65625 -1.53125 -1.8125q-0.53125 -1.171875 -0.53125 -2.6875q0 -1.46875 0.484375 -2.671875q0.5 -1.203125 1.46875 -1.84375q0.984375 -0.640625 2.203125 -0.640625q0.890625 0 1.578125 0.375q0.703125 0.375 1.140625 0.984375l0 -4.875l1.65625 0l0 13.59375l-1.546875 0zm-5.28125 -4.921875q0 1.890625 0.796875 2.828125q0.8125 0.9375 1.890625 0.9375q1.09375 0 1.859375 -0.890625q0.765625 -0.890625 0.765625 -2.734375q0 -2.015625 -0.78125 -2.953125q-0.78125 -0.953125 -1.921875 -0.953125q-1.109375 0 -1.859375 0.90625q-0.75 0.90625 -0.75 2.859375z" fill-rule="nonzero"></path><path fill="#efefef" d="m408.5328 17.057743l147.21262 0l0 59.748028l-147.21262 0z" fill-rule="nonzero"></path><path stroke="#999999" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m408.5328 17.057743l147.21262 0l0 59.748028l-147.21262 0z" fill-rule="nonzero"></path><path fill="#000000" d="m455.20813 40.258007l1.796875 0l0 7.84375q0 2.0625 -0.46875 3.265625q-0.453125 1.203125 -1.671875 1.96875q-1.203125 0.75 -3.171875 0.75q-1.90625 0 -3.125 -0.65625q-1.21875 -0.65625 -1.734375 -1.90625q-0.515625 -1.25 -0.515625 -3.421875l0 -7.84375l1.796875 0l0 7.84375q0 1.765625 0.328125 2.609375q0.328125 0.84375 1.125 1.296875q0.8125 0.453125 1.96875 0.453125q1.984375 0 2.828125 -0.890625q0.84375 -0.90625 0.84375 -3.46875l0 -7.84375zm4.332306 13.59375l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0zm10.391357 -11.6875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm7.785431 -1.5l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm10.382233 1.5l0 -12.0l-4.46875 0l0 -1.59375l10.765625 0l0 1.59375l-4.5 0l0 12.0l-1.796875 0zm14.474121 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.438202 2.9375l1.65625 -0.265625q0.140625 1.0 0.765625 1.53125q0.640625 0.515625 1.78125 0.515625q1.15625 0 1.703125 -0.46875q0.5625 -0.46875 0.5625 -1.09375q0 -0.5625 -0.484375 -0.890625q-0.34375 -0.21875 -1.703125 -0.5625q-1.84375 -0.46875 -2.5625 -0.796875q-0.703125 -0.34375 -1.078125 -0.9375q-0.359375 -0.609375 -0.359375 -1.328125q0 -0.65625 0.296875 -1.21875q0.3125 -0.5625 0.828125 -0.9375q0.390625 -0.28125 1.0625 -0.484375q0.671875 -0.203125 1.4375 -0.203125q1.171875 0 2.046875 0.34375q0.875 0.328125 1.28125 0.90625q0.421875 0.5625 0.578125 1.515625l-1.625 0.21875q-0.109375 -0.75 -0.65625 -1.171875q-0.53125 -0.4375 -1.5 -0.4375q-1.15625 0 -1.640625 0.390625q-0.484375 0.375 -0.484375 0.875q0 0.328125 0.203125 0.59375q0.203125 0.265625 0.640625 0.4375q0.25 0.09375 1.46875 0.4375q1.765625 0.46875 2.46875 0.765625q0.703125 0.296875 1.09375 0.875q0.40625 0.578125 0.40625 1.4375q0 0.828125 -0.484375 1.578125q-0.484375 0.734375 -1.40625 1.140625q-0.921875 0.390625 -2.078125 0.390625q-1.921875 0 -2.9375 -0.796875q-1.0 -0.796875 -1.28125 -2.359375zm13.65625 1.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125z" fill-rule="nonzero"></path><path fill="#efefef" d="m594.1181 37.30971l147.21259 0l0 59.748028l-147.21259 0z" fill-rule="nonzero"></path><path stroke="#999999" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m594.1181 37.30971l147.21259 0l0 59.748028l-147.21259 0z" fill-rule="nonzero"></path><path fill="#000000" d="m625.4092 63.103725l0 -13.59375l1.8125 0l0 13.59375l-1.8125 0zm4.6676636 0l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0zm14.031982 -1.5l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm8.277039 -1.671875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.813232 6.6875l1.609375 0.24999619q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.2812462q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.46875 -1.109375 -3.515625q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.515625q0 2.3124962 -0.46875 3.2656212q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.3906212zm1.375 -5.921875q0 1.953125 0.765625 2.84375q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.890625 0.78125 -2.78125q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875zm9.281982 5.109375l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0zm12.6657715 -1.21875q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.03125q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.328125 0.09375 2.953125q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.71875q-0.90625 0.359375 -2.734375 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.671875 0.5 1.125q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.15625q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm7.7351074 3.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm1.5426636 -10.1875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm3.5042114 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.281982 4.921875l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0z" fill-rule="nonzero"></path><path fill="#000000" d="m654.5047 85.10372l0 -12.0l-4.46875 0l0 -1.59375l10.765625 0l0 1.59375l-4.5 0l0 12.0l-1.796875 0zm14.474121 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.438232 2.9375l1.65625 -0.265625q0.140625 1.0 0.765625 1.53125q0.640625 0.515625 1.78125 0.515625q1.15625 0 1.703125 -0.46875q0.5625 -0.46875 0.5625 -1.09375q0 -0.5625 -0.484375 -0.890625q-0.34375 -0.21875 -1.703125 -0.5625q-1.84375 -0.46875 -2.5625 -0.796875q-0.703125 -0.34375 -1.078125 -0.9375q-0.359375 -0.609375 -0.359375 -1.328125q0 -0.65625 0.296875 -1.21875q0.3125 -0.5625 0.828125 -0.9375q0.390625 -0.28125 1.0625 -0.484375q0.671875 -0.203125 1.4375 -0.203125q1.171875 0 2.046875 0.34375q0.875 0.328125 1.28125 0.90625q0.421875 0.5625 0.578125 1.515625l-1.625 0.21875q-0.109375 -0.75 -0.65625 -1.171875q-0.53125 -0.4375 -1.5 -0.4375q-1.15625 0 -1.640625 0.390625q-0.484375 0.375 -0.484375 0.875q0 0.328125 0.203125 0.59375q0.203125 0.265625 0.640625 0.4375q0.25 0.09375 1.46875 0.4375q1.765625 0.46875 2.46875 0.765625q0.703125 0.296875 1.09375 0.875q0.40625 0.578125 0.40625 1.4375q0 0.828125 -0.484375 1.578125q-0.484375 0.734375 -1.40625 1.140625q-0.921875 0.390625 -2.078125 0.390625q-1.921875 0 -2.9375 -0.796875q-1.0 -0.796875 -1.28125 -2.359375zm13.65625 1.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125z" fill-rule="nonzero"></path><path fill="#efefef" d="m584.5302 27.199474l147.21259 0l0 59.748035l-147.21259 0z" fill-rule="nonzero"></path><path stroke="#999999" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m584.5302 27.199474l147.21259 0l0 59.748035l-147.21259 0z" fill-rule="nonzero"></path><path fill="#000000" d="m615.8212 52.99349l0 -13.59375l1.8125 0l0 13.59375l-1.8125 0zm4.6677246 0l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0zm14.031921 -1.5l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm8.2771 -1.671875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.813171 6.6875l1.609375 0.25q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.28125q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.46875 -1.109375 -3.515625q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.515625q0 2.3125 -0.46875 3.265625q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.390625zm1.375 -5.921875q0 1.953125 0.765625 2.84375q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.890625 0.78125 -2.78125q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875zm9.281982 5.109375l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0zm12.6658325 -1.21875q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.03125q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.328125 0.09375 2.953125q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.71875q-0.90625 0.359375 -2.734375 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.671875 0.5 1.125q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.15625q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm7.7350464 3.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm1.5427246 -10.1875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm3.5041504 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.281982 4.921875l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0z" fill-rule="nonzero"></path><path fill="#000000" d="m644.9168 74.99349l0 -12.000004l-4.46875 0l0 -1.59375l10.765625 0l0 1.59375l-4.5 0l0 12.000004l-1.796875 0zm14.474121 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.438171 2.9375l1.65625 -0.265625q0.140625 1.0 0.765625 1.53125q0.640625 0.515625 1.78125 0.515625q1.15625 0 1.703125 -0.46875q0.5625 -0.46875 0.5625 -1.09375q0 -0.5625 -0.484375 -0.890625q-0.34375 -0.21875 -1.703125 -0.5625q-1.84375 -0.46875 -2.5625 -0.796875q-0.703125 -0.34375 -1.078125 -0.9375q-0.359375 -0.609375 -0.359375 -1.328125q0 -0.65625 0.296875 -1.21875q0.3125 -0.5625 0.828125 -0.9375q0.390625 -0.28125 1.0625 -0.484375q0.671875 -0.203125 1.4375 -0.203125q1.171875 0 2.046875 0.34375q0.875 0.328125 1.28125 0.90625q0.421875 0.5625 0.578125 1.515625l-1.625 0.21875q-0.109375 -0.75 -0.65625 -1.171875q-0.53125 -0.4375 -1.5 -0.4375q-1.15625 0 -1.640625 0.390625q-0.484375 0.375 -0.484375 0.875q0 0.328125 0.203125 0.59375q0.203125 0.265625 0.640625 0.4375q0.25 0.09375 1.46875 0.4375q1.765625 0.46875 2.46875 0.765625q0.703125 0.296875 1.09375 0.875q0.40625 0.578125 0.40625 1.4375q0 0.828125 -0.484375 1.578125q-0.484375 0.734375 -1.40625 1.140625q-0.921875 0.390625 -2.078125 0.390625q-1.921875 0 -2.9375 -0.796875q-1.0 -0.796875 -1.28125 -2.359375zm13.65625 1.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375038l1.65625 -1.0l0 3.4375038l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125z" fill-rule="nonzero"></path><path fill="#efefef" d="m574.93176 17.057743l147.21259 0l0 59.748028l-147.21259 0z" fill-rule="nonzero"></path><path stroke="#999999" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m574.93176 17.057743l147.21259 0l0 59.748028l-147.21259 0z" fill-rule="nonzero"></path><path fill="#000000" d="m606.22284 42.851757l0 -13.59375l1.8125 0l0 13.59375l-1.8125 0zm4.6676636 0l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0zm14.031982 -1.5l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm8.277039 -1.671875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.813232 6.6875l1.609375 0.25q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.28125q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.46875 -1.109375 -3.515625q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.515625q0 2.3125 -0.46875 3.265625q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.390625zm1.375 -5.921875q0 1.953125 0.765625 2.84375q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.890625 0.78125 -2.78125q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875zm9.281982 5.109375l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0zm12.6657715 -1.21875q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.03125q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.328125 0.09375 2.953125q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.71875q-0.90625 0.359375 -2.734375 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.671875 0.5 1.125q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.15625q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm7.7351074 3.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm1.5426636 -10.1875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm3.5042114 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.281982 4.921875l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0z" fill-rule="nonzero"></path><path fill="#000000" d="m635.31836 64.85175l0 -11.999996l-4.46875 0l0 -1.59375l10.765625 0l0 1.59375l-4.5 0l0 11.999996l-1.796875 0zm14.474121 -3.1718712l1.71875 0.21875q-0.40625 1.5 -1.515625 2.3437462q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.3281212q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.438232 2.9375l1.65625 -0.265625q0.140625 1.0 0.765625 1.53125q0.640625 0.515625 1.78125 0.515625q1.15625 0 1.703125 -0.46875q0.5625 -0.46875 0.5625 -1.09375q0 -0.5625 -0.484375 -0.890625q-0.34375 -0.21875 -1.703125 -0.5625q-1.84375 -0.46875 -2.5625 -0.796875q-0.703125 -0.34375 -1.078125 -0.9375q-0.359375 -0.609375 -0.359375 -1.328125q0 -0.65625 0.296875 -1.21875q0.3125 -0.5625 0.828125 -0.9375q0.390625 -0.28125 1.0625 -0.484375q0.671875 -0.203125 1.4375 -0.203125q1.171875 0 2.046875 0.34375q0.875 0.328125 1.28125 0.90625q0.421875 0.5625 0.578125 1.515625l-1.625 0.21875q-0.109375 -0.75 -0.65625 -1.171875q-0.53125 -0.4375 -1.5 -0.4375q-1.15625 0 -1.640625 0.390625q-0.484375 0.375 -0.484375 0.875q0 0.328125 0.203125 0.59375q0.203125 0.265625 0.640625 0.4375q0.25 0.09375 1.46875 0.4375q1.765625 0.46875 2.46875 0.765625q0.703125 0.296875 1.09375 0.875q0.40625 0.578125 0.40625 1.4375q0 0.828125 -0.484375 1.578125q-0.484375 0.7343712 -1.40625 1.1406212q-0.921875 0.390625 -2.078125 0.390625q-1.921875 0 -2.9375 -0.796875q-1.0 -0.7968712 -1.28125 -2.3593712zm13.65625 1.4375l0.234375 1.4843712q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.7499962q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m389.34647 46.93176l9.593231 0l0 0.062992096l9.58786 0" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m389.34647 46.93176l9.593201 0l0 0.062992096l3.5878906 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m402.52756 48.646484l4.538086 -1.6517334l-4.538086 -1.6517334z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m555.7454 46.93176l9.593201 0l0 0.062992096l9.587891 0" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m555.7454 46.93176l9.593201 0l0 0.062992096l3.5878906 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m568.9265 48.646484l4.538086 -1.6517334l-4.538086 -1.6517334z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m214.96326 207.26509l355.29132 0l0 0.06298828l355.29138 0" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m214.96326 207.26509l355.29132 0l0 0.06298828l349.29138 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m919.54596 208.97981l4.538086 -1.6517334l-4.538086 -1.6517334z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m214.93439 136.81526l14.661118 0l0 0.06300354l14.661713 0" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m214.93439 136.81526l14.661133 0l0 0.06300354l8.661697 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m238.25722 138.53l4.538101 -1.6517334l-4.538101 -1.6517334z" fill-rule="evenodd"></path><path fill="#efefef" d="m244.25691 106.94125l147.2126 0l0 59.74803l-147.2126 0z" fill-rule="nonzero"></path><path stroke="#999999" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m244.25691 106.94125l147.2126 0l0 59.74803l-147.2126 0z" fill-rule="nonzero"></path><path fill="#000000" d="m298.5115 143.73526l0 -13.59375l5.109375 0q1.546875 0 2.484375 0.40625q0.953125 0.40625 1.484375 1.265625q0.53125 0.859375 0.53125 1.796875q0 0.875 -0.46875 1.65625q-0.46875 0.765625 -1.4375 1.234375q1.234375 0.359375 1.890625 1.234375q0.671875 0.875 0.671875 2.0625q0 0.953125 -0.40625 1.78125q-0.390625 0.8125 -0.984375 1.265625q-0.59375 0.4375 -1.5 0.671875q-0.890625 0.21875 -2.1875 0.21875l-5.1875 0zm1.796875 -7.890625l2.9375 0q1.203125 0 1.71875 -0.15625q0.6875 -0.203125 1.03125 -0.671875q0.359375 -0.46875 0.359375 -1.1875q0 -0.671875 -0.328125 -1.1875q-0.328125 -0.515625 -0.9375 -0.703125q-0.59375 -0.203125 -2.0625 -0.203125l-2.71875 0l0 4.109375zm0 6.28125l3.390625 0q0.875 0 1.21875 -0.0625q0.625 -0.109375 1.046875 -0.359375q0.421875 -0.265625 0.6875 -0.765625q0.265625 -0.5 0.265625 -1.140625q0 -0.765625 -0.390625 -1.328125q-0.390625 -0.5625 -1.078125 -0.78125q-0.6875 -0.234375 -1.984375 -0.234375l-3.15625 0l0 4.671875zm16.959198 1.609375l0 -1.453125q-1.140625 1.671875 -3.125 1.671875q-0.859375 0 -1.625 -0.328125q-0.75 -0.34375 -1.125 -0.84375q-0.359375 -0.5 -0.515625 -1.234375q-0.09375 -0.5 -0.09375 -1.5625l0 -6.109375l1.671875 0l0 5.46875q0 1.3125 0.09375 1.765625q0.15625 0.65625 0.671875 1.03125q0.515625 0.375 1.265625 0.375q0.75 0 1.40625 -0.375q0.65625 -0.390625 0.921875 -1.046875q0.28125 -0.671875 0.28125 -1.9375l0 -5.28125l1.671875 0l0 9.859375l-1.5 0zm3.9382324 -11.6875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm4.097931 0l0 -13.59375l1.671875 0l0 13.59375l-1.671875 0zm10.566711 0l0 -1.25q-0.9375 1.46875 -2.75 1.46875q-1.171875 0 -2.171875 -0.640625q-0.984375 -0.65625 -1.53125 -1.8125q-0.53125 -1.171875 -0.53125 -2.6875q0 -1.46875 0.484375 -2.671875q0.5 -1.203125 1.46875 -1.84375q0.984375 -0.640625 2.203125 -0.640625q0.890625 0 1.578125 0.375q0.703125 0.375 1.140625 0.984375l0 -4.875l1.65625 0l0 13.59375l-1.546875 0zm-5.28125 -4.921875q0 1.890625 0.796875 2.828125q0.8125 0.9375 1.890625 0.9375q1.09375 0 1.859375 -0.890625q0.765625 -0.890625 0.765625 -2.734375q0 -2.015625 -0.78125 -2.953125q-0.78125 -0.953125 -1.921875 -0.953125q-1.109375 0 -1.859375 0.90625q-0.75 0.90625 -0.75 2.859375z" fill-rule="nonzero"></path><path fill="#efefef" d="m410.65585 106.94125l147.21262 0l0 59.74803l-147.21262 0z" fill-rule="nonzero"></path><path stroke="#999999" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m410.65585 106.94125l147.21262 0l0 59.74803l-147.21262 0z" fill-rule="nonzero"></path><path fill="#000000" d="m457.33118 130.14151l1.796875 0l0 7.84375q0 2.0625 -0.46875 3.265625q-0.453125 1.203125 -1.671875 1.96875q-1.203125 0.75 -3.171875 0.75q-1.90625 0 -3.125 -0.65625q-1.21875 -0.65625 -1.734375 -1.90625q-0.515625 -1.25 -0.515625 -3.421875l0 -7.84375l1.796875 0l0 7.84375q0 1.765625 0.328125 2.609375q0.328125 0.84375 1.125 1.296875q0.8125 0.453125 1.96875 0.453125q1.984375 0 2.828125 -0.890625q0.84375 -0.90625 0.84375 -3.46875l0 -7.84375zm4.332306 13.59375l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0zm10.391357 -11.6875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm7.785431 -1.5l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm10.382233 1.5l0 -12.0l-4.46875 0l0 -1.59375l10.765625 0l0 1.59375l-4.5 0l0 12.0l-1.796875 0zm14.474121 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.438202 2.9375l1.65625 -0.265625q0.140625 1.0 0.765625 1.53125q0.640625 0.515625 1.78125 0.515625q1.15625 0 1.703125 -0.46875q0.5625 -0.46875 0.5625 -1.09375q0 -0.5625 -0.484375 -0.890625q-0.34375 -0.21875 -1.703125 -0.5625q-1.84375 -0.46875 -2.5625 -0.796875q-0.703125 -0.34375 -1.078125 -0.9375q-0.359375 -0.609375 -0.359375 -1.328125q0 -0.65625 0.296875 -1.21875q0.3125 -0.5625 0.828125 -0.9375q0.390625 -0.28125 1.0625 -0.484375q0.671875 -0.203125 1.4375 -0.203125q1.171875 0 2.046875 0.34375q0.875 0.328125 1.28125 0.90625q0.421875 0.5625 0.578125 1.515625l-1.625 0.21875q-0.109375 -0.75 -0.65625 -1.171875q-0.53125 -0.4375 -1.5 -0.4375q-1.15625 0 -1.640625 0.390625q-0.484375 0.375 -0.484375 0.875q0 0.328125 0.203125 0.59375q0.203125 0.265625 0.640625 0.4375q0.25 0.09375 1.46875 0.4375q1.765625 0.46875 2.46875 0.765625q0.703125 0.296875 1.09375 0.875q0.40625 0.578125 0.40625 1.4375q0 0.828125 -0.484375 1.578125q-0.484375 0.734375 -1.40625 1.140625q-0.921875 0.390625 -2.078125 0.390625q-1.921875 0 -2.9375 -0.796875q-1.0 -0.796875 -1.28125 -2.359375zm13.65625 1.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125z" fill-rule="nonzero"></path><path fill="#efefef" d="m596.24115 127.19322l147.21259 0l0 59.74803l-147.21259 0z" fill-rule="nonzero"></path><path stroke="#999999" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m596.24115 127.19322l147.21259 0l0 59.74803l-147.21259 0z" fill-rule="nonzero"></path><path fill="#000000" d="m627.5322 152.98723l0 -13.59375l1.8125 0l0 13.59375l-1.8125 0zm4.6676636 0l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0zm14.031982 -1.5l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm8.277039 -1.671875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.813232 6.6875l1.609375 0.25q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.28125q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.46875 -1.109375 -3.515625q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.515625q0 2.3125 -0.46875 3.265625q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.390625zm1.375 -5.921875q0 1.953125 0.765625 2.84375q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.890625 0.78125 -2.78125q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875zm9.281982 5.109375l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0zm12.6657715 -1.21875q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.03125q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.328125 0.09375 2.953125q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.71875q-0.90625 0.359375 -2.734375 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.671875 0.5 1.125q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.15625q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm7.7351074 3.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm1.5426636 -10.1875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm3.5042114 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.281982 4.921875l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0z" fill-rule="nonzero"></path><path fill="#000000" d="m656.62775 174.98723l0 -12.0l-4.46875 0l0 -1.59375l10.765625 0l0 1.59375l-4.5 0l0 12.0l-1.796875 0zm14.474121 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.438232 2.9375l1.65625 -0.265625q0.140625 1.0 0.765625 1.53125q0.640625 0.515625 1.78125 0.515625q1.15625 0 1.703125 -0.46875q0.5625 -0.46875 0.5625 -1.09375q0 -0.5625 -0.484375 -0.890625q-0.34375 -0.21875 -1.703125 -0.5625q-1.84375 -0.46875 -2.5625 -0.796875q-0.703125 -0.34375 -1.078125 -0.9375q-0.359375 -0.609375 -0.359375 -1.328125q0 -0.65625 0.296875 -1.21875q0.3125 -0.5625 0.828125 -0.9375q0.390625 -0.28125 1.0625 -0.484375q0.671875 -0.203125 1.4375 -0.203125q1.171875 0 2.046875 0.34375q0.875 0.328125 1.28125 0.90625q0.421875 0.5625 0.578125 1.515625l-1.625 0.21875q-0.109375 -0.75 -0.65625 -1.171875q-0.53125 -0.4375 -1.5 -0.4375q-1.15625 0 -1.640625 0.390625q-0.484375 0.375 -0.484375 0.875q0 0.328125 0.203125 0.59375q0.203125 0.265625 0.640625 0.4375q0.25 0.09375 1.46875 0.4375q1.765625 0.46875 2.46875 0.765625q0.703125 0.296875 1.09375 0.875q0.40625 0.578125 0.40625 1.4375q0 0.828125 -0.484375 1.578125q-0.484375 0.734375 -1.40625 1.140625q-0.921875 0.390625 -2.078125 0.390625q-1.921875 0 -2.9375 -0.796875q-1.0 -0.796875 -1.28125 -2.359375zm13.65625 1.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125z" fill-rule="nonzero"></path><path fill="#efefef" d="m586.65326 117.082985l147.21259 0l0 59.748024l-147.21259 0z" fill-rule="nonzero"></path><path stroke="#999999" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m586.65326 117.082985l147.21259 0l0 59.748024l-147.21259 0z" fill-rule="nonzero"></path><path fill="#000000" d="m617.9443 142.877l0 -13.59375l1.8125 0l0 13.59375l-1.8125 0zm4.6677246 0l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0zm14.031921 -1.5l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm8.2771 -1.671875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.813171 6.6875l1.609375 0.25q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.28125q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.46875 -1.109375 -3.515625q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.515625q0 2.3125 -0.46875 3.265625q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.390625zm1.375 -5.921875q0 1.953125 0.765625 2.84375q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.890625 0.78125 -2.78125q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875zm9.281982 5.109375l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0zm12.6658325 -1.21875q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.03125q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.328125 0.09375 2.953125q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.71875q-0.90625 0.359375 -2.734375 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.671875 0.5 1.125q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.15625q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm7.7350464 3.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm1.5427246 -10.1875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm3.5041504 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.281982 4.921875l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0z" fill-rule="nonzero"></path><path fill="#000000" d="m647.03986 164.877l0 -12.0l-4.46875 0l0 -1.59375l10.765625 0l0 1.59375l-4.5 0l0 12.0l-1.796875 0zm14.474121 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.438171 2.9375l1.65625 -0.265625q0.140625 1.0 0.765625 1.53125q0.640625 0.515625 1.78125 0.515625q1.15625 0 1.703125 -0.46875q0.5625 -0.46875 0.5625 -1.09375q0 -0.5625 -0.484375 -0.890625q-0.34375 -0.21875 -1.703125 -0.5625q-1.84375 -0.46875 -2.5625 -0.796875q-0.703125 -0.34375 -1.078125 -0.9375q-0.359375 -0.609375 -0.359375 -1.328125q0 -0.65625 0.296875 -1.21875q0.3125 -0.5625 0.828125 -0.9375q0.390625 -0.28125 1.0625 -0.484375q0.671875 -0.203125 1.4375 -0.203125q1.171875 0 2.046875 0.34375q0.875 0.328125 1.28125 0.90625q0.421875 0.5625 0.578125 1.515625l-1.625 0.21875q-0.109375 -0.75 -0.65625 -1.171875q-0.53125 -0.4375 -1.5 -0.4375q-1.15625 0 -1.640625 0.390625q-0.484375 0.375 -0.484375 0.875q0 0.328125 0.203125 0.59375q0.203125 0.265625 0.640625 0.4375q0.25 0.09375 1.46875 0.4375q1.765625 0.46875 2.46875 0.765625q0.703125 0.296875 1.09375 0.875q0.40625 0.578125 0.40625 1.4375q0 0.828125 -0.484375 1.578125q-0.484375 0.734375 -1.40625 1.140625q-0.921875 0.390625 -2.078125 0.390625q-1.921875 0 -2.9375 -0.796875q-1.0 -0.796875 -1.28125 -2.359375zm13.65625 1.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125z" fill-rule="nonzero"></path><path fill="#efefef" d="m577.0548 106.94125l147.21259 0l0 59.74803l-147.21259 0z" fill-rule="nonzero"></path><path stroke="#999999" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m577.0548 106.94125l147.21259 0l0 59.74803l-147.21259 0z" fill-rule="nonzero"></path><path fill="#000000" d="m608.3459 132.73526l0 -13.593742l1.8125 0l0 13.593742l-1.8125 0zm4.6676636 0l0 -9.859367l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0624924l-1.671875 0l0 -5.9999924q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.3749924l-1.671875 0zm14.031982 -1.5l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.6562424l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.7499924q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm8.277039 -1.671875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.7343674q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.43749237l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.7031174l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.813232 6.6874924l1.609375 0.25q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.28125q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.46875 -1.109375 -3.5156174q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.515617q0 2.3125 -0.46875 3.265625q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.390625zm1.375 -5.9218674q0 1.9531174 0.765625 2.8437424q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.890625 0.78125 -2.7812424q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875zm9.281982 5.1093674l0 -9.859367l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.1562424l-1.671875 0zm12.6657715 -1.21875q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.03125q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.9531174q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.2343674q0 2.328125 0.09375 2.953125q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.7187424q-0.90625 0.35936737 -2.734375 0.6249924q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.671875 0.5 1.125q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.15625q0.265625 -0.578125 0.265625 -1.671875l0 -0.6093674zm7.7351074 3.4374924l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.6562424l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.7499924q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm1.5426636 -10.187492l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.687492l0 -9.859367l1.671875 0l0 9.859367l-1.671875 0zm3.5042114 -4.9218674q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.9062424 -0.578125 2.9999924q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8124924zm1.71875 0q0 1.8906174 0.828125 2.8281174q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.8906174q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.281982 4.9218674l0 -9.859367l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0624924l-1.671875 0l0 -5.9999924q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.3749924l-1.671875 0z" fill-rule="nonzero"></path><path fill="#000000" d="m637.4414 154.73526l0 -12.0l-4.46875 0l0 -1.59375l10.765625 0l0 1.59375l-4.5 0l0 12.0l-1.796875 0zm14.474121 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.438232 2.9375l1.65625 -0.265625q0.140625 1.0 0.765625 1.53125q0.640625 0.515625 1.78125 0.515625q1.15625 0 1.703125 -0.46875q0.5625 -0.46875 0.5625 -1.09375q0 -0.5625 -0.484375 -0.890625q-0.34375 -0.21875 -1.703125 -0.5625q-1.84375 -0.46875 -2.5625 -0.796875q-0.703125 -0.34375 -1.078125 -0.9375q-0.359375 -0.609375 -0.359375 -1.328125q0 -0.65625 0.296875 -1.21875q0.3125 -0.5625 0.828125 -0.9375q0.390625 -0.28125 1.0625 -0.484375q0.671875 -0.203125 1.4375 -0.203125q1.171875 0 2.046875 0.34375q0.875 0.328125 1.28125 0.90625q0.421875 0.5625 0.578125 1.515625l-1.625 0.21875q-0.109375 -0.75 -0.65625 -1.171875q-0.53125 -0.4375 -1.5 -0.4375q-1.15625 0 -1.640625 0.390625q-0.484375 0.375 -0.484375 0.875q0 0.328125 0.203125 0.59375q0.203125 0.265625 0.640625 0.4375q0.25 0.09375 1.46875 0.4375q1.765625 0.46875 2.46875 0.765625q0.703125 0.296875 1.09375 0.875q0.40625 0.578125 0.40625 1.4375q0 0.828125 -0.484375 1.578125q-0.484375 0.734375 -1.40625 1.140625q-0.921875 0.390625 -2.078125 0.390625q-1.921875 0 -2.9375 -0.796875q-1.0 -0.796875 -1.28125 -2.359375zm13.65625 1.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m391.4695 136.81526l9.593231 0l0 0.06300354l9.58786 0" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m391.46954 136.81526l9.593201 0l0 0.06300354l3.5878906 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m404.65063 138.53l4.538086 -1.6517334l-4.538086 -1.6517334z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m557.86847 136.81526l9.593201 0l0 0.06300354l9.587891 0" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m557.86847 136.81526l9.593201 0l0 0.06300354l3.5878906 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m571.04956 138.53l4.538147 -1.6517334l-4.538147 -1.6517334z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m724.2674 136.81526l20.531738 0l0 0.06300354l20.539124 0" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m724.2674 136.81526l20.531738 0l0 0.06300354l14.539124 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m759.33826 138.53l4.538086 -1.6517334l-4.538086 -1.6517334z" fill-rule="evenodd"></path></g></svg>
+
diff --git a/doc/ci/img/types-of-pipelines.svg b/doc/ci/img/types-of-pipelines.svg
new file mode 100644
index 00000000000..b63b5f56ba6
--- /dev/null
+++ b/doc/ci/img/types-of-pipelines.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" standalone="yes"?>
+
+<svg version="1.1" viewBox="0.0 0.0 740.6272965879265 293.7795275590551" fill="none" stroke="none" stroke-linecap="square" stroke-miterlimit="10" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="p.0"><path d="m0 0l740.6273 0l0 293.77954l-740.6273 0l0 -293.77954z" clip-rule="nonzero"></path></clipPath><g clip-path="url(#p.0)"><path fill="#000000" fill-opacity="0.0" d="m0 0l740.6273 0l0 293.77954l-740.6273 0z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m176.05511 4.632546l282.4567 0l0 129.10236l-282.4567 0z" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" stroke-dasharray="4.0,3.0" d="m176.05511 4.632546l282.4567 0l0 129.10236l-282.4567 0z" fill-rule="nonzero"></path><path fill="#ff0000" d="m283.34512 115.88928l1.796875 0.453125q-0.5625 2.21875 -2.03125 3.390625q-1.46875 1.15625 -3.59375 1.15625q-2.203125 0 -3.578125 -0.890625q-1.375 -0.90625 -2.09375 -2.59375q-0.71875 -1.703125 -0.71875 -3.65625q0 -2.125 0.796875 -3.703125q0.8125 -1.578125 2.3125 -2.390625q1.5 -0.828125 3.296875 -0.828125q2.046875 0 3.4375 1.046875q1.390625 1.03125 1.9375 2.90625l-1.765625 0.421875q-0.46875 -1.484375 -1.375 -2.15625q-0.90625 -0.6875 -2.265625 -0.6875q-1.5625 0 -2.625 0.75q-1.046875 0.75 -1.484375 2.03125q-0.421875 1.265625 -0.421875 2.609375q0 1.734375 0.5 3.03125q0.515625 1.28125 1.578125 1.921875q1.078125 0.640625 2.3125 0.640625q1.515625 0 2.5625 -0.859375q1.046875 -0.875 1.421875 -2.59375zm4.066681 4.765625l0 -13.59375l1.8125 0l0 13.59375l-1.8125 0zm10.069733 0l0 -13.59375l5.125 0q1.359375 0 2.078125 0.125q1.0 0.171875 1.671875 0.640625q0.671875 0.46875 1.078125 1.3125q0.421875 0.84375 0.421875 1.84375q0 1.734375 -1.109375 2.9375q-1.09375 1.203125 -3.984375 1.203125l-3.484375 0l0 5.53125l-1.796875 0zm1.796875 -7.140625l3.515625 0q1.75 0 2.46875 -0.640625q0.734375 -0.65625 0.734375 -1.828125q0 -0.859375 -0.4375 -1.46875q-0.421875 -0.609375 -1.125 -0.796875q-0.453125 -0.125 -1.671875 -0.125l-3.484375 0l0 4.859375zm10.443573 -4.546875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm4.1292114 3.78125l0 -13.640625l1.53125 0l0 1.28125q0.53125 -0.75 1.203125 -1.125q0.6875 -0.375 1.640625 -0.375q1.265625 0 2.234375 0.65625q0.96875 0.640625 1.453125 1.828125q0.5 1.1875 0.5 2.59375q0 1.515625 -0.546875 2.734375q-0.546875 1.203125 -1.578125 1.84375q-1.03125 0.640625 -2.171875 0.640625q-0.84375 0 -1.515625 -0.34375q-0.65625 -0.359375 -1.078125 -0.890625l0 4.796875l-1.671875 0zm1.515625 -8.65625q0 1.90625 0.765625 2.8125q0.78125 0.90625 1.875 0.90625q1.109375 0 1.890625 -0.9375q0.796875 -0.9375 0.796875 -2.921875q0 -1.875 -0.78125 -2.8125q-0.765625 -0.9375 -1.84375 -0.9375q-1.0625 0 -1.890625 1.0q-0.8125 1.0 -0.8125 2.890625zm15.610077 1.703125l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm9.078857 5.875l0 -13.59375l1.671875 0l0 13.59375l-1.671875 0zm4.191681 -11.6875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm4.1292114 0l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0zm17.125702 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875z" fill-rule="nonzero"></path><path fill="#f3f3f3" d="m482.78796 22.986877l110.11023 0l0 59.74803l-110.11023 0z" fill-rule="nonzero"></path><path stroke="#b7b7b7" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m482.78796 22.986877l110.11023 0l0 59.74803l-110.11023 0z" fill-rule="nonzero"></path><path fill="#434343" d="m499.90414 48.78089l0 -13.59375l4.6875 0q1.578125 0 2.421875 0.1875q1.15625 0.265625 1.984375 0.96875q1.078125 0.921875 1.609375 2.34375q0.53125 1.40625 0.53125 3.21875q0 1.546875 -0.359375 2.75q-0.359375 1.1875 -0.921875 1.984375q-0.5625 0.78125 -1.234375 1.234375q-0.671875 0.4375 -1.625 0.671875q-0.953125 0.234375 -2.1875 0.234375l-4.90625 0zm1.796875 -1.609375l2.90625 0q1.34375 0 2.109375 -0.25q0.765625 -0.25 1.21875 -0.703125q0.640625 -0.640625 1.0 -1.71875q0.359375 -1.078125 0.359375 -2.625q0 -2.125 -0.703125 -3.265625q-0.703125 -1.15625 -1.703125 -1.546875q-0.71875 -0.28125 -2.328125 -0.28125l-2.859375 0l0 10.390625zm18.207306 -1.5625l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm9.110107 9.65625l0 -13.640625l1.53125 0l0 1.28125q0.53125 -0.75 1.203125 -1.125q0.6875 -0.375 1.640625 -0.375q1.265625 0 2.234375 0.65625q0.96875 0.640625 1.453125 1.828125q0.5 1.1875 0.5 2.59375q0 1.515625 -0.546875 2.734375q-0.546875 1.203125 -1.578125 1.84375q-1.03125 0.640625 -2.171875 0.640625q-0.84375 0 -1.515625 -0.34375q-0.65625 -0.359375 -1.078125 -0.890625l0 4.796875l-1.671875 0zm1.515625 -8.65625q0 1.90625 0.765625 2.8125q0.78125 0.90625 1.875 0.90625q1.109375 0 1.890625 -0.9375q0.796875 -0.9375 0.796875 -2.921875q0 -1.875 -0.78125 -2.8125q-0.765625 -0.9375 -1.84375 -0.9375q-1.0625 0 -1.890625 1.0q-0.8125 1.0 -0.8125 2.890625zm8.828857 4.875l0 -13.59375l1.671875 0l0 13.59375l-1.671875 0zm3.5510864 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.203796 8.71875l-0.171875 -1.5625q0.546875 0.140625 0.953125 0.140625q0.546875 0 0.875 -0.1875q0.34375 -0.1875 0.5625 -0.515625q0.15625 -0.25 0.5 -1.25q0.046875 -0.140625 0.15625 -0.40625l-3.734375 -9.875l1.796875 0l2.046875 5.71875q0.40625 1.078125 0.71875 2.28125q0.28125 -1.15625 0.6875 -2.25l2.09375 -5.75l1.671875 0l-3.75 10.03125q-0.59375 1.625 -0.9375 2.234375q-0.4375 0.828125 -1.015625 1.203125q-0.578125 0.390625 -1.375 0.390625q-0.484375 0 -1.078125 -0.203125zm18.245789 -5.296875l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm0.9020996 -3.421875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125z" fill-rule="nonzero"></path><path fill="#434343" d="m507.0652 66.40589l1.6875 -0.140625q0.125 1.015625 0.5625 1.671875q0.4375 0.65625 1.359375 1.0625q0.9375 0.40625 2.09375 0.40625q1.03125 0 1.8125 -0.3125q0.796875 -0.3125 1.1875 -0.84375q0.390625 -0.53125 0.390625 -1.15625q0 -0.640625 -0.375 -1.109375q-0.375 -0.484375 -1.234375 -0.8125q-0.546875 -0.21875 -2.421875 -0.65625q-1.875 -0.453125 -2.625 -0.859375q-0.96875 -0.515625 -1.453125 -1.265625q-0.46875 -0.75 -0.46875 -1.6875q0 -1.03125 0.578125 -1.921875q0.59375 -0.90625 1.703125 -1.359375q1.125 -0.46875 2.5 -0.46875q1.515625 0 2.671875 0.484375q1.15625 0.484375 1.765625 1.4375q0.625 0.9375 0.671875 2.140625l-1.71875 0.125q-0.140625 -1.28125 -0.953125 -1.9375q-0.796875 -0.671875 -2.359375 -0.671875q-1.625 0 -2.375 0.609375q-0.75 0.59375 -0.75 1.4375q0 0.734375 0.53125 1.203125q0.515625 0.46875 2.703125 0.96875q2.203125 0.5 3.015625 0.875q1.1875 0.546875 1.75 1.390625q0.578125 0.828125 0.578125 1.921875q0 1.09375 -0.625 2.0625q-0.625 0.953125 -1.796875 1.484375q-1.15625 0.53125 -2.609375 0.53125q-1.84375 0 -3.09375 -0.53125q-1.25 -0.546875 -1.96875 -1.625q-0.703125 -1.078125 -0.734375 -2.453125zm16.490417 2.875l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm7.9645996 0.28125q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.03125q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.328125 0.09375 2.953125q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.71875q-0.90625 0.359375 -2.734375 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.671875 0.5 1.125q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.15625q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm3.7819214 5.75l1.609375 0.25q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.28125q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.46875 -1.109375 -3.515625q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.515625q0 2.3125 -0.46875 3.265625q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.390625zm1.375 -5.921875q0 1.953125 0.765625 2.84375q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.890625 0.78125 -2.78125q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875zm9.313232 -6.578125l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm4.1292114 0l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0zm10.078796 0.8125l1.609375 0.25q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.28125q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.46875 -1.109375 -3.515625q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.515625q0 2.3125 -0.46875 3.265625q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.390625zm1.375 -5.921875q0 1.953125 0.765625 2.84375q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.890625 0.78125 -2.78125q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875z" fill-rule="nonzero"></path><path fill="#f3f3f3" d="m606.1778 23.019379l110.11023 0l0 59.74803l-110.11023 0z" fill-rule="nonzero"></path><path stroke="#b7b7b7" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m606.1778 23.019379l110.11023 0l0 59.74803l-110.11023 0z" fill-rule="nonzero"></path><path fill="#434343" d="m623.29395 48.813393l0 -13.59375l4.6875 0q1.578125 0 2.421875 0.1875q1.15625 0.265625 1.984375 0.96875q1.078125 0.921875 1.609375 2.34375q0.53125 1.40625 0.53125 3.21875q0 1.546875 -0.359375 2.75q-0.359375 1.1875 -0.921875 1.984375q-0.5625 0.78125 -1.234375 1.234375q-0.671875 0.4375 -1.625 0.671875q-0.953125 0.234375 -2.1875 0.234375l-4.90625 0zm1.796875 -1.609375l2.90625 0q1.34375 0 2.109375 -0.25q0.765625 -0.25 1.21875 -0.703125q0.640625 -0.640625 1.0 -1.71875q0.359375 -1.078125 0.359375 -2.625q0 -2.125 -0.703125 -3.265625q-0.703125 -1.15625 -1.703125 -1.546875q-0.71875 -0.28125 -2.328125 -0.28125l-2.859375 0l0 10.390625zm18.207336 -1.5625l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm9.110107 9.65625l0 -13.640625l1.53125 0l0 1.28125q0.53125 -0.75 1.203125 -1.125q0.6875 -0.375 1.640625 -0.375q1.265625 0 2.234375 0.65625q0.96875 0.640625 1.453125 1.828125q0.5 1.1875 0.5 2.59375q0 1.515625 -0.546875 2.734375q-0.546875 1.203125 -1.578125 1.84375q-1.03125 0.640625 -2.171875 0.640625q-0.84375 0 -1.515625 -0.34375q-0.65625 -0.359375 -1.078125 -0.890625l0 4.796875l-1.671875 0zm1.515625 -8.65625q0 1.90625 0.765625 2.8125q0.78125 0.90625 1.875 0.90625q1.109375 0 1.890625 -0.9375q0.796875 -0.9375 0.796875 -2.921875q0 -1.875 -0.78125 -2.8125q-0.765625 -0.9375 -1.84375 -0.9375q-1.0625 0 -1.890625 1.0q-0.8125 1.0 -0.8125 2.890625zm8.828857 4.875l0 -13.59375l1.671875 0l0 13.59375l-1.671875 0zm3.5510254 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.203857 8.71875l-0.171875 -1.5625q0.546875 0.140625 0.953125 0.140625q0.546875 0 0.875 -0.1875q0.34375 -0.1875 0.5625 -0.515625q0.15625 -0.25 0.5 -1.25q0.046875 -0.140625 0.15625 -0.40625l-3.734375 -9.875l1.796875 0l2.046875 5.71875q0.40625 1.078125 0.71875 2.28125q0.28125 -1.15625 0.6875 -2.25l2.09375 -5.75l1.671875 0l-3.75 10.03125q-0.59375 1.625 -0.9375 2.234375q-0.4375 0.828125 -1.015625 1.203125q-0.578125 0.390625 -1.375 0.390625q-0.484375 0 -1.078125 -0.203125zm18.245789 -5.296875l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm0.9020996 -3.421875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125z" fill-rule="nonzero"></path><path fill="#434343" d="m618.10614 70.81339l0 -13.59375l5.125 0q1.359375 0 2.078125 0.125q1.0 0.171875 1.671875 0.640625q0.671875 0.46875 1.078125 1.3125q0.421875 0.84375 0.421875 1.84375q0 1.734375 -1.109375 2.9375q-1.09375 1.203125 -3.984375 1.203125l-3.484375 0l0 5.53125l-1.796875 0zm1.796875 -7.140625l3.515625 0q1.75 0 2.46875 -0.640625q0.734375 -0.65625 0.734375 -1.828125q0 -0.859375 -0.4375 -1.46875q-0.421875 -0.609375 -1.125 -0.796875q-0.453125 -0.125 -1.671875 -0.125l-3.484375 0l0 4.859375zm10.4122925 7.140625l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0zm5.6033325 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm15.672546 4.921875l0 -1.25q-0.9375 1.46875 -2.75 1.46875q-1.171875 0 -2.171875 -0.640625q-0.984375 -0.65625 -1.53125 -1.8125q-0.53125 -1.171875 -0.53125 -2.6875q0 -1.46875 0.484375 -2.671875q0.5 -1.203125 1.46875 -1.84375q0.984375 -0.640625 2.203125 -0.640625q0.890625 0 1.578125 0.375q0.703125 0.375 1.140625 0.984375l0 -4.875l1.65625 0l0 13.59375l-1.546875 0zm-5.28125 -4.921875q0 1.890625 0.796875 2.828125q0.8125 0.9375 1.890625 0.9375q1.09375 0 1.859375 -0.890625q0.765625 -0.890625 0.765625 -2.734375q0 -2.015625 -0.78125 -2.953125q-0.78125 -0.953125 -1.921875 -0.953125q-1.109375 0 -1.859375 0.90625q-0.75 0.90625 -0.75 2.859375zm15.719482 4.921875l0 -1.453125q-1.140625 1.671875 -3.125 1.671875q-0.859375 0 -1.625 -0.328125q-0.75 -0.34375 -1.125 -0.84375q-0.359375 -0.5 -0.515625 -1.234375q-0.09375 -0.5 -0.09375 -1.5625l0 -6.109375l1.671875 0l0 5.46875q0 1.3125 0.09375 1.765625q0.15625 0.65625 0.671875 1.03125q0.515625 0.375 1.265625 0.375q0.75 0 1.40625 -0.375q0.65625 -0.390625 0.921875 -1.046875q0.28125 -0.671875 0.28125 -1.9375l0 -5.28125l1.671875 0l0 9.859375l-1.5 0zm10.360107 -3.609375l1.640625 0.21875q-0.265625 1.6875 -1.375 2.65625q-1.109375 0.953125 -2.734375 0.953125q-2.015625 0 -3.25 -1.3125q-1.21875 -1.328125 -1.21875 -3.796875q0 -1.59375 0.515625 -2.78125q0.53125 -1.203125 1.609375 -1.796875q1.09375 -0.609375 2.359375 -0.609375q1.609375 0 2.625 0.8125q1.015625 0.8125 1.3125 2.3125l-1.625 0.25q-0.234375 -1.0 -0.828125 -1.5q-0.59375 -0.5 -1.421875 -0.5q-1.265625 0 -2.0625 0.90625q-0.78125 0.90625 -0.78125 2.859375q0 1.984375 0.765625 2.890625q0.765625 0.890625 1.984375 0.890625q0.984375 0 1.640625 -0.59375q0.65625 -0.609375 0.84375 -1.859375zm6.546875 2.109375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm1.5426636 -10.1875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm3.5042114 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.281982 4.921875l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0z" fill-rule="nonzero"></path><path fill="#f3f3f3" d="m192.52992 22.986877l110.07875 0l0 59.74803l-110.07875 0z" fill-rule="nonzero"></path><path stroke="#b7b7b7" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m192.52992 22.986877l110.07875 0l0 59.74803l-110.07875 0z" fill-rule="nonzero"></path><path fill="#434343" d="m228.21759 59.78089l0 -13.59375l5.109375 0q1.546875 0 2.484375 0.40625q0.953125 0.40625 1.484375 1.265625q0.53125 0.859375 0.53125 1.796875q0 0.875 -0.46875 1.65625q-0.46875 0.765625 -1.4375 1.234375q1.234375 0.359375 1.890625 1.234375q0.671875 0.875 0.671875 2.0625q0 0.953125 -0.40625 1.78125q-0.390625 0.8125 -0.984375 1.265625q-0.59375 0.4375 -1.5 0.671875q-0.890625 0.21875 -2.1875 0.21875l-5.1875 0zm1.796875 -7.890625l2.9375 0q1.203125 0 1.71875 -0.15625q0.6875 -0.203125 1.03125 -0.671875q0.359375 -0.46875 0.359375 -1.1875q0 -0.671875 -0.328125 -1.1875q-0.328125 -0.515625 -0.9375 -0.703125q-0.59375 -0.203125 -2.0625 -0.203125l-2.71875 0l0 4.109375zm0 6.28125l3.390625 0q0.875 0 1.21875 -0.0625q0.625 -0.109375 1.046875 -0.359375q0.421875 -0.265625 0.6875 -0.765625q0.265625 -0.5 0.265625 -1.140625q0 -0.765625 -0.390625 -1.328125q-0.390625 -0.5625 -1.078125 -0.78125q-0.6875 -0.234375 -1.984375 -0.234375l-3.15625 0l0 4.671875zm16.959198 1.609375l0 -1.453125q-1.140625 1.671875 -3.125 1.671875q-0.859375 0 -1.625 -0.328125q-0.75 -0.34375 -1.125 -0.84375q-0.359375 -0.5 -0.515625 -1.234375q-0.09375 -0.5 -0.09375 -1.5625l0 -6.109375l1.671875 0l0 5.46875q0 1.3125 0.09375 1.765625q0.15625 0.65625 0.671875 1.03125q0.515625 0.375 1.265625 0.375q0.75 0 1.40625 -0.375q0.65625 -0.390625 0.921875 -1.046875q0.28125 -0.671875 0.28125 -1.9375l0 -5.28125l1.671875 0l0 9.859375l-1.5 0zm3.9382172 -11.6875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm4.097946 0l0 -13.59375l1.671875 0l0 13.59375l-1.671875 0zm10.566681 0l0 -1.25q-0.9375 1.46875 -2.75 1.46875q-1.171875 0 -2.171875 -0.640625q-0.984375 -0.65625 -1.53125 -1.8125q-0.53125 -1.171875 -0.53125 -2.6875q0 -1.46875 0.484375 -2.671875q0.5 -1.203125 1.46875 -1.84375q0.984375 -0.640625 2.203125 -0.640625q0.890625 0 1.578125 0.375q0.703125 0.375 1.140625 0.984375l0 -4.875l1.65625 0l0 13.59375l-1.546875 0zm-5.28125 -4.921875q0 1.890625 0.796875 2.828125q0.8125 0.9375 1.890625 0.9375q1.09375 0 1.859375 -0.890625q0.765625 -0.890625 0.765625 -2.734375q0 -2.015625 -0.78125 -2.953125q-0.78125 -0.953125 -1.921875 -0.953125q-1.109375 0 -1.859375 0.90625q-0.75 0.90625 -0.75 2.859375z" fill-rule="nonzero"></path><path fill="#f3f3f3" d="m331.2466 43.238846l110.078735 0l0 59.74803l-110.078735 0z" fill-rule="nonzero"></path><path stroke="#b7b7b7" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m331.2466 43.238846l110.078735 0l0 59.74803l-110.078735 0z" fill-rule="nonzero"></path><path fill="#434343" d="m343.97073 69.03286l0 -13.59375l1.8125 0l0 13.59375l-1.8125 0zm4.667694 0l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0zm14.031952 -1.5l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm8.277069 -1.671875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.813202 6.6875l1.609375 0.25q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.28125q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.46875 -1.109375 -3.515625q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.515625q0 2.3125 -0.46875 3.265625q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.390625zm1.375 -5.921875q0 1.953125 0.765625 2.84375q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.890625 0.78125 -2.78125q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875zm9.281982 5.109375l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0zm12.665802 -1.21875q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.03125q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.328125 0.09375 2.953125q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.71875q-0.90625 0.359375 -2.734375 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.671875 0.5 1.125q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.15625q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm7.735077 3.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm1.5426941 -10.1875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm3.504181 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.281982 4.921875l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0z" fill-rule="nonzero"></path><path fill="#434343" d="m373.06628 91.03286l0 -12.0l-4.46875 0l0 -1.59375l10.765625 0l0 1.59375l-4.5 0l0 12.0l-1.796875 0zm14.474121 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.438202 2.9375l1.65625 -0.265625q0.140625 1.0 0.765625 1.53125q0.640625 0.515625 1.78125 0.515625q1.15625 0 1.703125 -0.46875q0.5625 -0.46875 0.5625 -1.09375q0 -0.5625 -0.484375 -0.890625q-0.34375 -0.21875 -1.703125 -0.5625q-1.84375 -0.46875 -2.5625 -0.796875q-0.703125 -0.34375 -1.078125 -0.9375q-0.359375 -0.609375 -0.359375 -1.328125q0 -0.65625 0.296875 -1.21875q0.3125 -0.5625 0.828125 -0.9375q0.390625 -0.28125 1.0625 -0.484375q0.671875 -0.203125 1.4375 -0.203125q1.171875 0 2.046875 0.34375q0.875 0.328125 1.28125 0.90625q0.421875 0.5625 0.578125 1.515625l-1.625 0.21875q-0.109375 -0.75 -0.65625 -1.171875q-0.53125 -0.4375 -1.5 -0.4375q-1.15625 0 -1.640625 0.390625q-0.484375 0.375 -0.484375 0.875q0 0.328125 0.203125 0.59375q0.203125 0.265625 0.640625 0.4375q0.25 0.09375 1.46875 0.4375q1.765625 0.46875 2.46875 0.765625q0.703125 0.296875 1.09375 0.875q0.40625 0.578125 0.40625 1.4375q0 0.828125 -0.484375 1.578125q-0.484375 0.734375 -1.40625 1.140625q-0.921875 0.390625 -2.078125 0.390625q-1.921875 0 -2.9375 -0.796875q-1.0 -0.796875 -1.28125 -2.359375zm13.65625 1.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125z" fill-rule="nonzero"></path><path fill="#f3f3f3" d="m324.0777 33.12861l110.078766 0l0 59.74803l-110.078766 0z" fill-rule="nonzero"></path><path stroke="#b7b7b7" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m324.0777 33.12861l110.078766 0l0 59.74803l-110.078766 0z" fill-rule="nonzero"></path><path fill="#434343" d="m336.80185 58.922623l0 -13.59375l1.8125 0l0 13.59375l-1.8125 0zm4.667694 0l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0zm14.031952 -1.5l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm8.277069 -1.671875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.813202 6.6875l1.609375 0.25q0.109375 0.75 0.578125 1.09375q0.609375 0.453125 1.6875 0.453125q1.171875 0 1.796875 -0.46875q0.625 -0.453125 0.859375 -1.28125q0.125 -0.515625 0.109375 -2.15625q-1.09375 1.296875 -2.71875 1.296875q-2.03125 0 -3.15625 -1.46875q-1.109375 -1.46875 -1.109375 -3.515625q0 -1.40625 0.515625 -2.59375q0.515625 -1.203125 1.484375 -1.84375q0.96875 -0.65625 2.265625 -0.65625q1.75 0 2.875 1.40625l0 -1.1875l1.546875 0l0 8.515625q0 2.3125 -0.46875 3.265625q-0.46875 0.96875 -1.484375 1.515625q-1.015625 0.5625 -2.5 0.5625q-1.765625 0 -2.859375 -0.796875q-1.078125 -0.796875 -1.03125 -2.390625zm1.375 -5.921875q0 1.953125 0.765625 2.84375q0.78125 0.890625 1.9375 0.890625q1.140625 0 1.921875 -0.890625q0.78125 -0.890625 0.78125 -2.78125q0 -1.8125 -0.8125 -2.71875q-0.796875 -0.921875 -1.921875 -0.921875q-1.109375 0 -1.890625 0.90625q-0.78125 0.890625 -0.78125 2.671875zm9.281982 5.109375l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0zm12.6657715 -1.21875q-0.9375 0.796875 -1.796875 1.125q-0.8593445 0.3125 -1.8437195 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.03125q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.0312195 -0.25 2.9843445 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.9062195 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.2499695 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.328125 0.09375 2.953125q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.71875q-0.90625 0.359375 -2.7343445 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.671875 0.5 1.125q0.515625 0.4375 1.484375 0.4375q0.9687195 0 1.7187195 -0.421875q0.75 -0.4375 1.109375 -1.15625q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm7.7351074 3.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm1.5426636 -10.1875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm3.5042114 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.281952 4.921875l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0z" fill-rule="nonzero"></path><path fill="#434343" d="m365.8974 80.92262l0 -12.0l-4.46875 0l0 -1.59375l10.765625 0l0 1.59375l-4.5 0l0 12.0l-1.796875 0zm14.474091 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.438232 2.9375l1.65625 -0.265625q0.140625 1.0 0.765625 1.53125q0.640625 0.515625 1.78125 0.515625q1.15625 0 1.703125 -0.46875q0.5625 -0.46875 0.5625 -1.09375q0 -0.5625 -0.484375 -0.890625q-0.34375 -0.21875 -1.703125 -0.5625q-1.84375 -0.46875 -2.5625 -0.796875q-0.703125 -0.34375 -1.078125 -0.9375q-0.359375 -0.609375 -0.359375 -1.328125q0 -0.65625 0.296875 -1.21875q0.3125 -0.5625 0.828125 -0.9375q0.390625 -0.28125 1.0625 -0.484375q0.671875 -0.203125 1.4375 -0.203125q1.171875 0 2.046875 0.34375q0.875 0.328125 1.28125 0.90625q0.421875 0.5625 0.578125 1.515625l-1.625 0.21875q-0.109375 -0.75 -0.65625 -1.171875q-0.53125 -0.4375 -1.5 -0.4375q-1.15625 0 -1.640625 0.390625q-0.484375 0.375 -0.484375 0.875q0 0.328125 0.203125 0.59375q0.203125 0.265625 0.640625 0.4375q0.25 0.09375 1.46875 0.4375q1.765625 0.46875 2.46875 0.765625q0.703125 0.296875 1.09375 0.875q0.40625 0.578125 0.40625 1.4375q0 0.828125 -0.484375 1.578125q-0.484375 0.734375 -1.40625 1.140625q-0.921875 0.390625 -2.078125 0.390625q-1.921875 0 -2.9375 -0.796875q-1.0 -0.796875 -1.28125 -2.359375zm13.65625 1.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125z" fill-rule="nonzero"></path><path fill="#f3f3f3" d="m316.90097 22.986877l110.078735 0l0 59.74803l-110.078735 0z" fill-rule="nonzero"></path><path stroke="#b7b7b7" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m316.90097 22.986877l110.078735 0l0 59.74803l-110.078735 0z" fill-rule="nonzero"></path><path fill="#434343" d="m354.05658 59.78089l0 -12.0l-4.46875 0l0 -1.59375l10.765625 0l0 1.59375l-4.5 0l0 12.0l-1.796875 0zm14.474121 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm8.438202 2.9375l1.65625 -0.265625q0.140625 1.0 0.765625 1.53125q0.640625 0.515625 1.78125 0.515625q1.15625 0 1.703125 -0.46875q0.5625 -0.46875 0.5625 -1.09375q0 -0.5625 -0.484375 -0.890625q-0.34375 -0.21875 -1.703125 -0.5625q-1.84375 -0.46875 -2.5625 -0.796875q-0.703125 -0.34375 -1.078125 -0.9375q-0.359375 -0.609375 -0.359375 -1.328125q0 -0.65625 0.296875 -1.21875q0.3125 -0.5625 0.828125 -0.9375q0.390625 -0.28125 1.0625 -0.484375q0.671875 -0.203125 1.4375 -0.203125q1.171875 0 2.046875 0.34375q0.875 0.328125 1.28125 0.90625q0.421875 0.5625 0.578125 1.515625l-1.625 0.21875q-0.109375 -0.75 -0.65625 -1.171875q-0.53125 -0.4375 -1.5 -0.4375q-1.15625 0 -1.640625 0.390625q-0.484375 0.375 -0.484375 0.875q0 0.328125 0.203125 0.59375q0.203125 0.265625 0.640625 0.4375q0.25 0.09375 1.46875 0.4375q1.765625 0.46875 2.46875 0.765625q0.703125 0.296875 1.09375 0.875q0.40625 0.578125 0.40625 1.4375q0 0.828125 -0.484375 1.578125q-0.484375 0.734375 -1.40625 1.140625q-0.921875 0.390625 -2.078125 0.390625q-1.921875 0 -2.9375 -0.796875q-1.0 -0.796875 -1.28125 -2.359375zm13.65625 1.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm0.8551941 -1.4375l1.65625 -0.265625q0.140625 1.0 0.765625 1.53125q0.640625 0.515625 1.78125 0.515625q1.15625 0 1.703125 -0.46875q0.5625 -0.46875 0.5625 -1.09375q0 -0.5625 -0.484375 -0.890625q-0.34375 -0.21875 -1.703125 -0.5625q-1.84375 -0.46875 -2.5625 -0.796875q-0.703125 -0.34375 -1.078125 -0.9375q-0.359375 -0.609375 -0.359375 -1.328125q0 -0.65625 0.296875 -1.21875q0.3125 -0.5625 0.828125 -0.9375q0.390625 -0.28125 1.0625 -0.484375q0.671875 -0.203125 1.4375 -0.203125q1.171875 0 2.046875 0.34375q0.875 0.328125 1.28125 0.90625q0.421875 0.5625 0.578125 1.515625l-1.625 0.21875q-0.109375 -0.75 -0.65625 -1.171875q-0.53125 -0.4375 -1.5 -0.4375q-1.15625 0 -1.640625 0.390625q-0.484375 0.375 -0.484375 0.875q0 0.328125 0.203125 0.59375q0.203125 0.265625 0.640625 0.4375q0.25 0.09375 1.46875 0.4375q1.765625 0.46875 2.46875 0.765625q0.703125 0.296875 1.09375 0.875q0.40625 0.578125 0.40625 1.4375q0 0.828125 -0.484375 1.578125q-0.484375 0.734375 -1.40625 1.140625q-0.921875 0.390625 -2.078125 0.390625q-1.921875 0 -2.9375 -0.796875q-1.0 -0.796875 -1.28125 -2.359375z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m302.60867 52.860893l7.165344 0l0 0.062992096l7.165344 0" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m302.60864 52.860893l7.1653748 0l0 0.062992096l1.1653442 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m310.93936 54.57562l4.5381165 -1.6517334l-4.5381165 -1.6517334z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m426.9797 52.860893l27.904388 0l0 0.062992096l27.906647 0" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m426.9797 52.860893l27.904388 0l0 0.062992096l21.906616 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m476.7907 54.57562l4.5381165 -1.6517334l-4.5381165 -1.6517334z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m592.8982 52.860893l6.6398315 0l0 0.062992096l6.6514893 0" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m592.8982 52.860893l6.6398315 0l0 0.062992096l0.6515503 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m600.1896 54.57562l4.538086 -1.6517334l-4.538086 -1.6517334z" fill-rule="evenodd"></path><path fill="#f3f3f3" d="m26.104986 22.986877l110.11024 0l0 59.74803l-110.11024 0z" fill-rule="nonzero"></path><path stroke="#b7b7b7" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m26.104986 22.986877l110.11024 0l0 59.74803l-110.11024 0z" fill-rule="nonzero"></path><path fill="#434343" d="m70.03193 55.015266l1.796875 0.453125q-0.5625 2.21875 -2.03125 3.390625q-1.46875 1.15625 -3.59375 1.15625q-2.203125 0 -3.5781212 -0.890625q-1.375 -0.90625 -2.09375 -2.59375q-0.71875 -1.703125 -0.71875 -3.65625q0 -2.125 0.796875 -3.703125q0.8125 -1.578125 2.3125 -2.390625q1.4999962 -0.828125 3.2968712 -0.828125q2.046875 0 3.4375 1.046875q1.390625 1.03125 1.9375 2.90625l-1.765625 0.421875q-0.46875 -1.484375 -1.375 -2.15625q-0.90625 -0.6875 -2.265625 -0.6875q-1.5625 0 -2.6249962 0.75q-1.046875 0.75 -1.484375 2.03125q-0.421875 1.265625 -0.421875 2.609375q0 1.734375 0.5 3.03125q0.515625 1.28125 1.578125 1.921875q1.0781212 0.640625 2.3124962 0.640625q1.515625 0 2.5625 -0.859375q1.046875 -0.875 1.421875 -2.59375zm2.9260712 -0.15625q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm15.672592 4.921875l0 -1.25q-0.9375 1.46875 -2.75 1.46875q-1.171875 0 -2.171875 -0.640625q-0.984375 -0.65625 -1.53125 -1.8125q-0.53125 -1.171875 -0.53125 -2.6875q0 -1.46875 0.484375 -2.671875q0.5 -1.203125 1.46875 -1.84375q0.984375 -0.640625 2.203125 -0.640625q0.890625 0 1.578125 0.375q0.703125 0.375 1.140625 0.984375l0 -4.875l1.65625 0l0 13.59375l-1.546875 0zm-5.28125 -4.921875q0 1.890625 0.796875 2.828125q0.8125 0.9375 1.890625 0.9375q1.09375 0 1.859375 -0.890625q0.765625 -0.890625 0.765625 -2.734375q0 -2.015625 -0.78125 -2.953125q-0.78125 -0.953125 -1.921875 -0.953125q-1.109375 0 -1.859375 0.90625q-0.75 0.90625 -0.75 2.859375zm16.016342 1.75l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m136.21523 52.860893l28.15747 0l0 0.062992096l28.157486 0" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m136.21523 52.860893l28.15747 0l0 0.062992096l22.157486 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m186.53018 54.57562l4.538101 -1.6517334l-4.538101 -1.6517334z" fill-rule="evenodd"></path><path fill="#f3f3f3" d="m26.104986 120.98688l110.11024 0l0 59.74803l-110.11024 0z" fill-rule="nonzero"></path><path stroke="#b7b7b7" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m26.104986 120.98688l110.11024 0l0 59.74803l-110.11024 0z" fill-rule="nonzero"></path><path fill="#434343" d="m50.508137 146.78088l0 -13.59375l6.03125 0q1.8125 0 2.75 0.359375q0.953125 0.359375 1.515625 1.296875q0.5625 0.921875 0.5625 2.046875q0 1.453125 -0.9375 2.453125q-0.921875 0.984375 -2.890625 1.25q0.71875 0.34375 1.09375 0.671875q0.78125 0.734375 1.484375 1.8125l2.375 3.703125l-2.265625 0l-1.796875 -2.828125q-0.796875 -1.21875 -1.3125 -1.875q-0.5 -0.65625 -0.90625 -0.90625q-0.40625 -0.265625 -0.8125 -0.359375q-0.3125 -0.078125 -1.015625 -0.078125l-2.078125 0l0 6.046875l-1.796875 0zm1.796875 -7.59375l3.859375 0q1.234375 0 1.921875 -0.25q0.703125 -0.265625 1.0625 -0.828125q0.375 -0.5625 0.375 -1.21875q0 -0.96875 -0.703125 -1.578125q-0.703125 -0.625 -2.21875 -0.625l-4.296875 0l0 4.5zm18.176067 4.421875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.2656212 -1.328125 -1.2656212 -3.734375q0 -2.484375 1.2656212 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm9.078842 5.875l0 -13.59375l1.671875 0l0 13.59375l-1.671875 0zm10.613571 -1.21875q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.03125q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.328125 0.09375 2.953125q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.71875q-0.90625 0.359375 -2.734375 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.671875 0.5 1.125q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.15625q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm7.7351 3.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.4062576 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.6562576 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm8.277054 -1.671875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm15.500717 5.875l0 -1.25q-0.9375 1.46875 -2.75 1.46875q-1.171875 0 -2.171875 -0.640625q-0.984375 -0.65625 -1.53125 -1.8125q-0.53125 -1.171875 -0.53125 -2.6875q0 -1.46875 0.484375 -2.671875q0.5 -1.203125 1.46875 -1.84375q0.984375 -0.640625 2.203125 -0.640625q0.890625 0 1.578125 0.375q0.703125 0.375 1.140625 0.984375l0 -4.875l1.65625 0l0 13.59375l-1.546875 0zm-5.28125 -4.921875q0 1.890625 0.796875 2.828125q0.8125 0.9375 1.890625 0.9375q1.09375 0 1.859375 -0.890625q0.765625 -0.890625 0.765625 -2.734375q0 -2.015625 -0.78125 -2.953125q-0.78125 -0.953125 -1.921875 -0.953125q-1.109375 0 -1.859375 0.90625q-0.75 0.90625 -0.75 2.859375z" fill-rule="nonzero"></path><path fill="#434343" d="m70.03193 164.01526l1.796875 0.453125q-0.5625 2.21875 -2.03125 3.390625q-1.46875 1.15625 -3.59375 1.15625q-2.203125 0 -3.5781212 -0.890625q-1.375 -0.90625 -2.09375 -2.59375q-0.71875 -1.703125 -0.71875 -3.65625q0 -2.125 0.796875 -3.703125q0.8125 -1.578125 2.3125 -2.390625q1.4999962 -0.828125 3.2968712 -0.828125q2.046875 0 3.4375 1.046875q1.390625 1.03125 1.9375 2.90625l-1.765625 0.421875q-0.46875 -1.484375 -1.375 -2.15625q-0.90625 -0.6875 -2.265625 -0.6875q-1.5625 0 -2.6249962 0.75q-1.046875 0.75 -1.484375 2.03125q-0.421875 1.265625 -0.421875 2.609375q0 1.734375 0.5 3.03125q0.515625 1.28125 1.578125 1.921875q1.0781212 0.640625 2.3124962 0.640625q1.515625 0 2.5625 -0.859375q1.046875 -0.875 1.421875 -2.59375zm2.9260712 -0.15625q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm15.672592 4.921875l0 -1.25q-0.9375 1.46875 -2.75 1.46875q-1.171875 0 -2.171875 -0.640625q-0.984375 -0.65625 -1.53125 -1.8125q-0.53125 -1.171875 -0.53125 -2.6875q0 -1.46875 0.484375 -2.671875q0.5 -1.203125 1.46875 -1.84375q0.984375 -0.640625 2.203125 -0.640625q0.890625 0 1.578125 0.375q0.703125 0.375 1.140625 0.984375l0 -4.875l1.65625 0l0 13.59375l-1.546875 0zm-5.28125 -4.921875q0 1.890625 0.796875 2.828125q0.8125 0.9375 1.890625 0.9375q1.09375 0 1.859375 -0.890625q0.765625 -0.890625 0.765625 -2.734375q0 -2.015625 -0.78125 -2.953125q-0.78125 -0.953125 -1.921875 -0.953125q-1.109375 0 -1.859375 0.90625q-0.75 0.90625 -0.75 2.859375zm16.016342 1.75l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m136.21523 150.86089l28.15747 0l0 -98.01574l28.157486 0" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m136.21523 150.86089l28.15747 0l0 -98.01574l22.157486 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m186.53018 54.496876l4.538101 -1.6517296l-4.538101 -1.6517334z" fill-rule="evenodd"></path><path fill="#f3f3f3" d="m26.104986 190.98688l110.11024 0l0 59.74803l-110.11024 0z" fill-rule="nonzero"></path><path stroke="#b7b7b7" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m26.104986 190.98688l110.11024 0l0 59.74803l-110.11024 0z" fill-rule="nonzero"></path><path fill="#434343" d="m50.508137 216.78088l0 -13.59375l6.03125 0q1.8125 0 2.75 0.359375q0.953125 0.359375 1.515625 1.296875q0.5625 0.921875 0.5625 2.046875q0 1.453125 -0.9375 2.453125q-0.921875 0.984375 -2.890625 1.25q0.71875 0.34375 1.09375 0.671875q0.78125 0.734375 1.484375 1.8125l2.375 3.703125l-2.265625 0l-1.796875 -2.828125q-0.796875 -1.21875 -1.3125 -1.875q-0.5 -0.65625 -0.90625 -0.90625q-0.40625 -0.265625 -0.8125 -0.359375q-0.3125 -0.078125 -1.015625 -0.078125l-2.078125 0l0 6.046875l-1.796875 0zm1.796875 -7.59375l3.859375 0q1.234375 0 1.921875 -0.25q0.703125 -0.265625 1.0625 -0.828125q0.375 -0.5625 0.375 -1.21875q0 -0.96875 -0.703125 -1.578125q-0.703125 -0.625 -2.21875 -0.625l-4.296875 0l0 4.5zm18.176067 4.421875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.2656212 -1.328125 -1.2656212 -3.734375q0 -2.484375 1.2656212 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm9.078842 5.875l0 -13.59375l1.671875 0l0 13.59375l-1.671875 0zm10.613571 -1.21875q-0.9375 0.796875 -1.796875 1.125q-0.859375 0.3125 -1.84375 0.3125q-1.609375 0 -2.484375 -0.78125q-0.875 -0.796875 -0.875 -2.03125q0 -0.734375 0.328125 -1.328125q0.328125 -0.59375 0.859375 -0.953125q0.53125 -0.359375 1.203125 -0.546875q0.5 -0.140625 1.484375 -0.25q2.03125 -0.25 2.984375 -0.578125q0 -0.34375 0 -0.4375q0 -1.015625 -0.46875 -1.4375q-0.640625 -0.5625 -1.90625 -0.5625q-1.171875 0 -1.734375 0.40625q-0.5625 0.40625 -0.828125 1.46875l-1.640625 -0.234375q0.234375 -1.046875 0.734375 -1.6875q0.515625 -0.640625 1.46875 -0.984375q0.96875 -0.359375 2.25 -0.359375q1.265625 0 2.046875 0.296875q0.78125 0.296875 1.15625 0.75q0.375 0.453125 0.515625 1.140625q0.09375 0.421875 0.09375 1.53125l0 2.234375q0 2.328125 0.09375 2.953125q0.109375 0.609375 0.4375 1.171875l-1.75 0q-0.265625 -0.515625 -0.328125 -1.21875zm-0.140625 -3.71875q-0.90625 0.359375 -2.734375 0.625q-1.03125 0.140625 -1.453125 0.328125q-0.421875 0.1875 -0.65625 0.546875q-0.234375 0.359375 -0.234375 0.796875q0 0.671875 0.5 1.125q0.515625 0.4375 1.484375 0.4375q0.96875 0 1.71875 -0.421875q0.75 -0.4375 1.109375 -1.15625q0.265625 -0.578125 0.265625 -1.671875l0 -0.609375zm7.7351 3.4375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.4062576 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.6562576 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm8.277054 -1.671875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm15.500717 5.875l0 -1.25q-0.9375 1.46875 -2.75 1.46875q-1.171875 0 -2.171875 -0.640625q-0.984375 -0.65625 -1.53125 -1.8125q-0.53125 -1.171875 -0.53125 -2.6875q0 -1.46875 0.484375 -2.671875q0.5 -1.203125 1.46875 -1.84375q0.984375 -0.640625 2.203125 -0.640625q0.890625 0 1.578125 0.375q0.703125 0.375 1.140625 0.984375l0 -4.875l1.65625 0l0 13.59375l-1.546875 0zm-5.28125 -4.921875q0 1.890625 0.796875 2.828125q0.8125 0.9375 1.890625 0.9375q1.09375 0 1.859375 -0.890625q0.765625 -0.890625 0.765625 -2.734375q0 -2.015625 -0.78125 -2.953125q-0.78125 -0.953125 -1.921875 -0.953125q-1.109375 0 -1.859375 0.90625q-0.75 0.90625 -0.75 2.859375z" fill-rule="nonzero"></path><path fill="#434343" d="m70.03193 234.01526l1.796875 0.453125q-0.5625 2.21875 -2.03125 3.390625q-1.46875 1.15625 -3.59375 1.15625q-2.203125 0 -3.5781212 -0.890625q-1.375 -0.90625 -2.09375 -2.59375q-0.71875 -1.703125 -0.71875 -3.65625q0 -2.125 0.796875 -3.703125q0.8125 -1.578125 2.3125 -2.390625q1.4999962 -0.828125 3.2968712 -0.828125q2.046875 0 3.4375 1.046875q1.390625 1.03125 1.9375 2.90625l-1.765625 0.421875q-0.46875 -1.484375 -1.375 -2.15625q-0.90625 -0.6875 -2.265625 -0.6875q-1.5625 0 -2.6249962 0.75q-1.046875 0.75 -1.484375 2.03125q-0.421875 1.265625 -0.421875 2.609375q0 1.734375 0.5 3.03125q0.515625 1.28125 1.578125 1.921875q1.0781212 0.640625 2.3124962 0.640625q1.515625 0 2.5625 -0.859375q1.046875 -0.875 1.421875 -2.59375zm2.9260712 -0.15625q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm15.672592 4.921875l0 -1.25q-0.9375 1.46875 -2.75 1.46875q-1.171875 0 -2.171875 -0.640625q-0.984375 -0.65625 -1.53125 -1.8125q-0.53125 -1.171875 -0.53125 -2.6875q0 -1.46875 0.484375 -2.671875q0.5 -1.203125 1.46875 -1.84375q0.984375 -0.640625 2.203125 -0.640625q0.890625 0 1.578125 0.375q0.703125 0.375 1.140625 0.984375l0 -4.875l1.65625 0l0 13.59375l-1.546875 0zm-5.28125 -4.921875q0 1.890625 0.796875 2.828125q0.8125 0.9375 1.890625 0.9375q1.09375 0 1.859375 -0.890625q0.765625 -0.890625 0.765625 -2.734375q0 -2.015625 -0.78125 -2.953125q-0.78125 -0.953125 -1.921875 -0.953125q-1.109375 0 -1.859375 0.90625q-0.75 0.90625 -0.75 2.859375zm16.016342 1.75l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m136.21523 220.86089l28.15747 0l0 -168.0l28.157486 0" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m136.21523 220.86089l28.15747 0l0 -168.0l22.157486 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m186.53018 54.512627l4.538101 -1.6517334l-4.538101 -1.6517334z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m5.1522307 4.632546l165.85826 0l0 283.52756l-165.85826 0z" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" stroke-dasharray="4.0,3.0" d="m5.1522307 4.632546l165.85826 0l0 283.52756l-165.85826 0z" fill-rule="nonzero"></path><path fill="#ff0000" d="m24.73604 275.0801l0 -13.59375l5.125 0q1.359375 0 2.078125 0.125q0.9999981 0.171875 1.6718731 0.640625q0.671875 0.46875 1.078125 1.3125q0.421875 0.84375 0.421875 1.84375q0 1.734375 -1.109375 2.9375q-1.09375 1.203125 -3.984373 1.203125l-3.484375 0l0 5.53125l-1.796875 0zm1.796875 -7.140625l3.515625 0q1.75 0 2.468748 -0.640625q0.734375 -0.65625 0.734375 -1.828125q0 -0.859375 -0.4375 -1.46875q-0.421875 -0.609375 -1.1249981 -0.796875q-0.453125 -0.125 -1.671875 -0.125l-3.484375 0l0 4.859375zm10.412321 7.140625l0 -9.859375l1.5 0l0 1.5q0.578125 -1.046875 1.0625 -1.375q0.484375 -0.34375 1.078125 -0.34375q0.84375 0 1.71875 0.546875l-0.578125 1.546875q-0.609375 -0.359375 -1.234375 -0.359375q-0.546875 0 -0.984375 0.328125q-0.421875 0.328125 -0.609375 0.90625q-0.28125 0.890625 -0.28125 1.953125l0 5.15625l-1.671875 0zm5.603302 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.281967 -6.734375l0 -1.9375l1.65625 0l0 1.9375l-1.65625 0zm-2.125 15.484375l0.3125 -1.421875q0.5 0.125 0.796875 0.125q0.515625 0 0.765625 -0.34375q0.25 -0.328125 0.25 -1.6875l0 -10.359375l1.65625 0l0 10.390625q0 1.828125 -0.46875 2.546875q-0.59375 0.921875 -2.0 0.921875q-0.671875 0 -1.3125 -0.171875zm13.019821 -7.0l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm15.547592 2.265625l1.640625 0.21875q-0.265625 1.6875 -1.375 2.65625q-1.109375 0.953125 -2.734375 0.953125q-2.015625 0 -3.25 -1.3125q-1.21875 -1.328125 -1.21875 -3.796875q0 -1.59375 0.515625 -2.78125q0.53125 -1.203125 1.609375 -1.796875q1.09375 -0.609375 2.359375 -0.609375q1.609375 0 2.625 0.8125q1.015625 0.8125 1.3125 2.3125l-1.625 0.25q-0.234375 -1.0 -0.828125 -1.5q-0.59375 -0.5 -1.421875 -0.5q-1.265625 0 -2.0625 0.90625q-0.78125 0.90625 -0.78125 2.859375q0 1.984375 0.765625 2.890625q0.765625 0.890625 1.984375 0.890625q0.984375 0 1.640625 -0.59375q0.65625 -0.609375 0.84375 -1.859375zm6.546875 2.109375l0.234375 1.484375q-0.703125 0.140625 -1.265625 0.140625q-0.90625 0 -1.40625 -0.28125q-0.5 -0.296875 -0.703125 -0.75q-0.203125 -0.46875 -0.203125 -1.984375l0 -5.65625l-1.234375 0l0 -1.3125l1.234375 0l0 -2.4375l1.65625 -1.0l0 3.4375l1.6875 0l0 1.3125l-1.6875 0l0 5.75q0 0.71875 0.078125 0.921875q0.09375 0.203125 0.296875 0.328125q0.203125 0.125 0.578125 0.125q0.265625 0 0.734375 -0.078125zm6.9291077 1.5l0 -13.59375l5.125 0q1.359375 0 2.078125 0.125q1.0 0.171875 1.671875 0.640625q0.671875 0.46875 1.078125 1.3125q0.421875 0.84375 0.421875 1.84375q0 1.734375 -1.109375 2.9375q-1.09375 1.203125 -3.984375 1.203125l-3.484375 0l0 5.53125l-1.796875 0zm1.796875 -7.140625l3.515625 0q1.75 0 2.46875 -0.640625q0.734375 -0.65625 0.734375 -1.828125q0 -0.859375 -0.4375 -1.46875q-0.421875 -0.609375 -1.125 -0.796875q-0.453125 -0.125 -1.671875 -0.125l-3.484375 0l0 4.859375zm10.443573 -4.546875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm4.129196 3.78125l0 -13.640625l1.53125 0l0 1.28125q0.53125 -0.75 1.203125 -1.125q0.6875 -0.375 1.640625 -0.375q1.265625 0 2.234375 0.65625q0.96875 0.640625 1.453125 1.828125q0.5 1.1875 0.5 2.59375q0 1.515625 -0.546875 2.734375q-0.546875 1.203125 -1.578125 1.84375q-1.03125 0.640625 -2.171875 0.640625q-0.84375 0 -1.515625 -0.34375q-0.65625 -0.359375 -1.078125 -0.890625l0 4.796875l-1.671875 0zm1.515625 -8.65625q0 1.90625 0.765625 2.8125q0.78125 0.90625 1.875 0.90625q1.109375 0 1.890625 -0.9375q0.796875 -0.9375 0.796875 -2.921875q0 -1.875 -0.78125 -2.8125q-0.765625 -0.9375 -1.84375 -0.9375q-1.0625 0 -1.890625 1.0q-0.8125 1.0 -0.8125 2.890625zm15.610092 1.703125l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm9.078842 5.875l0 -13.59375l1.671875 0l0 13.59375l-1.671875 0zm4.191696 -11.6875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm4.129196 0l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0zm17.125732 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m464.70865 4.632546l270.23624 0l0 129.10236l-270.23624 0z" fill-rule="nonzero"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" stroke-dasharray="4.0,3.0" d="m464.70865 4.632546l270.23624 0l0 129.10236l-270.23624 0z" fill-rule="nonzero"></path><path fill="#ff0000" d="m536.47687 120.65491l0 -13.59375l4.6875 0q1.578125 0 2.421875 0.1875q1.15625 0.265625 1.984375 0.96875q1.078125 0.921875 1.609375 2.34375q0.53125 1.40625 0.53125 3.21875q0 1.546875 -0.359375 2.75q-0.359375 1.1875 -0.921875 1.984375q-0.5625 0.78125 -1.234375 1.234375q-0.671875 0.4375 -1.625 0.671875q-0.953125 0.234375 -2.1875 0.234375l-4.90625 0zm1.796875 -1.609375l2.90625 0q1.34375 0 2.109375 -0.25q0.765625 -0.25 1.21875 -0.703125q0.640625 -0.640625 1.0 -1.71875q0.359375 -1.078125 0.359375 -2.625q0 -2.125 -0.703125 -3.265625q-0.703125 -1.15625 -1.703125 -1.546875q-0.71875 -0.28125 -2.328125 -0.28125l-2.859375 0l0 10.390625zm18.207336 -1.5625l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm9.110107 9.65625l0 -13.640625l1.53125 0l0 1.28125q0.53125 -0.75 1.203125 -1.125q0.6875 -0.375 1.640625 -0.375q1.265625 0 2.234375 0.65625q0.96875 0.640625 1.453125 1.828125q0.5 1.1875 0.5 2.59375q0 1.515625 -0.546875 2.734375q-0.546875 1.203125 -1.578125 1.84375q-1.03125 0.640625 -2.171875 0.640625q-0.84375 0 -1.515625 -0.34375q-0.65625 -0.359375 -1.078125 -0.890625l0 4.796875l-1.671875 0zm1.515625 -8.65625q0 1.90625 0.765625 2.8125q0.78125 0.90625 1.875 0.90625q1.109375 0 1.890625 -0.9375q0.796875 -0.9375 0.796875 -2.921875q0 -1.875 -0.78125 -2.8125q-0.765625 -0.9375 -1.84375 -0.9375q-1.0625 0 -1.890625 1.0q-0.8125 1.0 -0.8125 2.890625zm8.828857 4.875l0 -13.59375l1.671875 0l0 13.59375l-1.671875 0zm3.5510254 -4.921875q0 -2.734375 1.53125 -4.0625q1.265625 -1.09375 3.09375 -1.09375q2.03125 0 3.3125 1.34375q1.296875 1.328125 1.296875 3.671875q0 1.90625 -0.578125 3.0q-0.5625 1.078125 -1.65625 1.6875q-1.078125 0.59375 -2.375 0.59375q-2.0625 0 -3.34375 -1.328125q-1.28125 -1.328125 -1.28125 -3.8125zm1.71875 0q0 1.890625 0.828125 2.828125q0.828125 0.9375 2.078125 0.9375q1.25 0 2.0625 -0.9375q0.828125 -0.953125 0.828125 -2.890625q0 -1.828125 -0.828125 -2.765625q-0.828125 -0.9375 -2.0625 -0.9375q-1.25 0 -2.078125 0.9375q-0.828125 0.9375 -0.828125 2.828125zm9.203857 8.71875l-0.171875 -1.5625q0.546875 0.140625 0.953125 0.140625q0.546875 0 0.875 -0.1875q0.34375 -0.1875 0.5625 -0.515625q0.15625 -0.25 0.5 -1.25q0.046875 -0.140625 0.15625 -0.40625l-3.734375 -9.875l1.796875 0l2.046875 5.71875q0.40625 1.078125 0.71875 2.28125q0.28125 -1.15625 0.6875 -2.25l2.09375 -5.75l1.671875 0l-3.75 10.03125q-0.59375 1.625 -0.9375 2.234375q-0.4375 0.828125 -1.015625 1.203125q-0.578125 0.390625 -1.375 0.390625q-0.484375 0 -1.078125 -0.203125zm14.808289 -3.796875l0 -13.59375l5.125 0q1.359375 0 2.078125 0.125q1.0 0.171875 1.671875 0.640625q0.671875 0.46875 1.078125 1.3125q0.421875 0.84375 0.421875 1.84375q0 1.734375 -1.109375 2.9375q-1.09375 1.203125 -3.984375 1.203125l-3.484375 0l0 5.53125l-1.796875 0zm1.796875 -7.140625l3.515625 0q1.75 0 2.46875 -0.640625q0.734375 -0.65625 0.734375 -1.828125q0 -0.859375 -0.4375 -1.46875q-0.421875 -0.609375 -1.125 -0.796875q-0.453125 -0.125 -1.671875 -0.125l-3.484375 0l0 4.859375zm10.4436035 -4.546875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm4.1292114 3.78125l0 -13.640625l1.53125 0l0 1.28125q0.53125 -0.75 1.203125 -1.125q0.6875 -0.375 1.640625 -0.375q1.265625 0 2.234375 0.65625q0.96875 0.640625 1.453125 1.828125q0.5 1.1875 0.5 2.59375q0 1.515625 -0.546875 2.734375q-0.546875 1.203125 -1.578125 1.84375q-1.03125 0.640625 -2.171875 0.640625q-0.84375 0 -1.515625 -0.34375q-0.65625 -0.359375 -1.078125 -0.890625l0 4.796875l-1.671875 0zm1.515625 -8.65625q0 1.90625 0.765625 2.8125q0.78125 0.90625 1.875 0.90625q1.109375 0 1.890625 -0.9375q0.796875 -0.9375 0.796875 -2.921875q0 -1.875 -0.78125 -2.8125q-0.765625 -0.9375 -1.84375 -0.9375q-1.0625 0 -1.890625 1.0q-0.8125 1.0 -0.8125 2.890625zm15.610046 1.703125l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875zm9.078857 5.875l0 -13.59375l1.671875 0l0 13.59375l-1.671875 0zm4.1917114 -11.6875l0 -1.90625l1.671875 0l0 1.90625l-1.671875 0zm0 11.6875l0 -9.859375l1.671875 0l0 9.859375l-1.671875 0zm4.1292114 0l0 -9.859375l1.5 0l0 1.40625q1.09375 -1.625 3.140625 -1.625q0.890625 0 1.640625 0.328125q0.75 0.3125 1.109375 0.84375q0.375 0.515625 0.53125 1.21875q0.09375 0.46875 0.09375 1.625l0 6.0625l-1.671875 0l0 -6.0q0 -1.015625 -0.203125 -1.515625q-0.1875 -0.515625 -0.6875 -0.8125q-0.5 -0.296875 -1.171875 -0.296875q-1.0625 0 -1.84375 0.671875q-0.765625 0.671875 -0.765625 2.578125l0 5.375l-1.671875 0zm17.125671 -3.171875l1.71875 0.21875q-0.40625 1.5 -1.515625 2.34375q-1.09375 0.828125 -2.8125 0.828125q-2.15625 0 -3.421875 -1.328125q-1.265625 -1.328125 -1.265625 -3.734375q0 -2.484375 1.265625 -3.859375q1.28125 -1.375 3.328125 -1.375q1.984375 0 3.234375 1.34375q1.25 1.34375 1.25 3.796875q0 0.140625 -0.015625 0.4375l-7.34375 0q0.09375 1.625 0.921875 2.484375q0.828125 0.859375 2.0625 0.859375q0.90625 0 1.546875 -0.46875q0.65625 -0.484375 1.046875 -1.546875zm-5.484375 -2.703125l5.5 0q-0.109375 -1.234375 -0.625 -1.859375q-0.796875 -0.96875 -2.078125 -0.96875q-1.140625 0 -1.9375 0.78125q-0.78125 0.765625 -0.859375 2.046875z" fill-rule="nonzero"></path></g></svg>
+
diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md
index 2c7c3ef3c18..db92a4b0d80 100644
--- a/doc/ci/pipelines.md
+++ b/doc/ci/pipelines.md
@@ -1,22 +1,44 @@
-# Introduction to pipelines and builds
+# Introduction to pipelines and jobs
>**Note:**
Introduced in GitLab 8.8.
## Pipelines
-A pipeline is a group of [builds][] that get executed in [stages][](batches).
-All of the builds in a stage are executed in parallel (if there are enough
+A pipeline is a group of [jobs][] that get executed in [stages][](batches).
+All of the jobs in a stage are executed in parallel (if there are enough
concurrent [Runners]), and if they all succeed, the pipeline moves on to the
-next stage. If one of the builds fails, the next stage is not (usually)
+next stage. If one of the jobs fails, the next stage is not (usually)
executed.
![Pipelines example](img/pipelines.png)
-## Builds
+## Types of Pipelines
-Builds are individual runs of [jobs]. Not to be confused with a `build` job or
-`build` stage.
+There are three types of pipelines that often use the single shorthand of "pipeline". People often talk about them as if each one is "the" pipeline, but really, they're just pieces of a single, comprehensive pipeline.
+
+![Types of Pipelines](img/types-of-pipelines.svg)
+
+1. **CI Pipeline**: Build and test stages defined in `.gitlab-ci.yml`
+2. **Deploy Pipeline**: Deploy stage(s) defined in `.gitlab-ci.yml` The flow of deploying code to servers through various stages: e.g. development to staging to production
+3. **Project Pipeline**: Cross-project CI dependencies [triggered via API][triggers], particularly for micro-services, but also for complicated build dependencies: e.g. api -> front-end, ce/ee -> omnibus.
+
+## Development Workflows
+
+Pipelines accommodate several development workflows:
+
+1. **Branch Flow** (e.g. different branch for dev, qa, staging, production)
+2. **Trunk-based Flow** (e.g. feature branches and single master branch, possibly with tags for releases)
+3. **Fork-based Flow** (e.g. merge requests come from forks)
+
+Example continuous delivery flow:
+
+![CD Flow](img/pipelines-goal.svg)
+
+## Jobs
+
+Jobs can be defined in the [`.gitlab-ci.yml`][jobs-yaml] file. Not to be
+confused with a `build` job or `build` stage.
## Defining pipelines
@@ -30,11 +52,11 @@ See full [documentation](yaml/README.md#jobs).
You can find the current and historical pipeline runs under **Pipelines** for
your project.
-## Seeing build status
+## Seeing job status
-Clicking on a pipeline will show the builds that were run for that pipeline.
-Clicking on an individual build will show you its build trace, and allow you to
-cancel the build, retry it, or erase the build trace.
+Clicking on a pipeline will show the jobs that were run for that pipeline.
+Clicking on an individual job will show you its job trace, and allow you to
+cancel the job, retry it, or erase the job trace.
## How the pipeline duration is calculated
@@ -69,11 +91,12 @@ total running time should be:
## Badges
-Build status and test coverage report badges are available. You can find their
+Pipeline status and test coverage report badges are available. You can find their
respective link in the [Pipelines settings] page.
-[builds]: #builds
-[jobs]: yaml/README.md#jobs
+[jobs]: #jobs
+[jobs-yaml]: yaml/README.md#jobs
[stages]: yaml/README.md#stages
[runners]: runners/README.html
[pipelines settings]: ../user/project/pipelines/settings.md
+[triggers]: triggers/README.md
diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md
index 1104edaabe9..2a5401ac13a 100644
--- a/doc/ci/quick_start/README.md
+++ b/doc/ci/quick_start/README.md
@@ -1,4 +1,4 @@
-# Quick Start
+# Getting started with GitLab CI
>**Note:** Starting from version 8.0, GitLab [Continuous Integration][ci] (CI)
is fully integrated into GitLab itself and is [enabled] by default on all
@@ -7,7 +7,7 @@ projects.
GitLab offers a [continuous integration][ci] service. If you
[add a `.gitlab-ci.yml` file][yaml] to the root directory of your repository,
and configure your GitLab project to use a [Runner], then each merge request or
-push triggers your CI [pipeline].
+push, triggers your CI [pipeline].
The `.gitlab-ci.yml` file tells the GitLab runner what to do. By default it runs
a pipeline with three [stages]: `build`, `test`, and `deploy`. You don't need to
@@ -31,13 +31,13 @@ So in brief, the steps needed to have a working CI can be summed up to:
From there on, on every push to your Git repository, the Runner will
automagically start the pipeline and the pipeline will appear under the
-project's `/pipelines` page.
+project's **Pipelines** page.
---
This guide assumes that you:
-- have a working GitLab instance of version 8.0 or higher or are using
+- have a working GitLab instance of version 8.0+r or are using
[GitLab.com](https://gitlab.com)
- have a project in GitLab that you would like to use CI for
@@ -54,7 +54,7 @@ The `.gitlab-ci.yml` file is where you configure what CI does with your project.
It lives in the root of your repository.
On any push to your repository, GitLab will look for the `.gitlab-ci.yml`
-file and start builds on _Runners_ according to the contents of the file,
+file and start jobs on _Runners_ according to the contents of the file,
for that commit.
Because `.gitlab-ci.yml` is in the repository and is version controlled, old
@@ -63,11 +63,12 @@ have different pipelines and jobs, and you have a single source of truth for CI.
You can read more about the reasons why we are using `.gitlab-ci.yml` [in our
blog about it][blog-ci].
-**Note:** `.gitlab-ci.yml` is a [YAML](https://en.wikipedia.org/wiki/YAML) file
-so you have to pay extra attention to indentation. Always use spaces, not tabs.
-
### Creating a simple `.gitlab-ci.yml` file
+>**Note:**
+`.gitlab-ci.yml` is a [YAML](https://en.wikipedia.org/wiki/YAML) file
+so you have to pay extra attention to indentation. Always use spaces, not tabs.
+
You need to create a file named `.gitlab-ci.yml` in the root directory of your
repository. Below is an example for a Ruby on Rails project.
@@ -88,7 +89,7 @@ rubocop:
- bundle exec rubocop
```
-This is the simplest possible build configuration that will work for most Ruby
+This is the simplest possible configuration that will work for most Ruby
applications:
1. Define two jobs `rspec` and `rubocop` (the names are arbitrary) with
@@ -98,22 +99,22 @@ applications:
The `.gitlab-ci.yml` file defines sets of jobs with constraints of how and when
they should be run. The jobs are defined as top-level elements with a name (in
our case `rspec` and `rubocop`) and always have to contain the `script` keyword.
-Jobs are used to create builds, which are then picked by
+Jobs are used to create jobs, which are then picked by
[Runners](../runners/README.md) and executed within the environment of the Runner.
What is important is that each job is run independently from each other.
If you want to check whether your `.gitlab-ci.yml` file is valid, there is a
Lint tool under the page `/ci/lint` of your GitLab instance. You can also find
-a "CI Lint" button to go to this page under **Pipelines > Pipelines** and
-**Pipelines > Builds** in your project.
+a "CI Lint" button to go to this page under **Pipelines ➔ Pipelines** and
+**Pipelines ➔ Jobs** in your project.
For more information and a complete `.gitlab-ci.yml` syntax, please read
-[the documentation on .gitlab-ci.yml](../yaml/README.md).
+[the reference documentation on .gitlab-ci.yml](../yaml/README.md).
### Push `.gitlab-ci.yml` to GitLab
-Once you've created `.gitlab-ci.yml`, you should add it to your git repository
+Once you've created `.gitlab-ci.yml`, you should add it to your Git repository
and push it to GitLab.
```bash
@@ -125,28 +126,27 @@ git push origin master
Now if you go to the **Pipelines** page you will see that the pipeline is
pending.
-You can also go to the **Commits** page and notice the little clock icon next
+You can also go to the **Commits** page and notice the little pause icon next
to the commit SHA.
![New commit pending](img/new_commit.png)
-Clicking on the clock icon you will be directed to the builds page for that
-specific commit.
+Clicking on it you will be directed to the jobs page for that specific commit.
-![Single commit builds page](img/single_commit_status_pending.png)
+![Single commit jobs page](img/single_commit_status_pending.png)
Notice that there are two jobs pending which are named after what we wrote in
`.gitlab-ci.yml`. The red triangle indicates that there is no Runner configured
-yet for these builds.
+yet for these jobs.
-The next step is to configure a Runner so that it picks the pending builds.
+The next step is to configure a Runner so that it picks the pending jobs.
## Configuring a Runner
-In GitLab, Runners run the builds that you define in `.gitlab-ci.yml`. A Runner
+In GitLab, Runners run the jobs that you define in `.gitlab-ci.yml`. A Runner
can be a virtual machine, a VPS, a bare-metal machine, a docker container or
even a cluster of containers. GitLab and the Runners communicate through an API,
-so the only requirement is that the Runner's machine has Internet access.
+so the only requirement is that the Runner's machine has [Internet] access.
A Runner can be specific to a certain project or serve multiple projects in
GitLab. If it serves all projects it's called a _Shared Runner_.
@@ -155,9 +155,9 @@ Find more information about different Runners in the
[Runners](../runners/README.md) documentation.
You can find whether any Runners are assigned to your project by going to
-**Settings > Runners**. Setting up a Runner is easy and straightforward. The
-official Runner supported by GitLab is written in Go and can be found at
-<https://gitlab.com/gitlab-org/gitlab-ci-multi-runner>.
+**Settings ➔ Runners**. Setting up a Runner is easy and straightforward. The
+official Runner supported by GitLab is written in Go and its documentation
+can be found at <https://docs.gitlab.com/runner/>.
In order to have a functional Runner you need to follow two steps:
@@ -167,28 +167,25 @@ In order to have a functional Runner you need to follow two steps:
Follow the links above to set up your own Runner or use a Shared Runner as
described in the next section.
-For other types of unofficial Runners written in other languages, see the
-[instructions for the various GitLab Runners](https://about.gitlab.com/gitlab-ci/#gitlab-runner).
-
Once the Runner has been set up, you should see it on the Runners page of your
-project, following **Settings > Runners**.
+project, following **Settings ➔ Runners**.
![Activated runners](img/runners_activated.png)
### Shared Runners
-If you use [GitLab.com](https://gitlab.com/) you can use **Shared Runners**
+If you use [GitLab.com](https://gitlab.com/) you can use the **Shared Runners**
provided by GitLab Inc.
These are special virtual machines that run on GitLab's infrastructure and can
build any project.
-To enable **Shared Runners** you have to go to your project's
-**Settings > Runners** and click **Enable shared runners**.
+To enable the **Shared Runners** you have to go to your project's
+**Settings ➔ Runners** and click **Enable shared runners**.
[Read more on Shared Runners](../runners/README.md).
-## Seeing the status of your pipeline and builds
+## Seeing the status of your pipeline and jobs
After configuring the Runner successfully, you should see the status of your
last commit change from _pending_ to either _running_, _success_ or _failed_.
@@ -197,23 +194,23 @@ You can view all pipelines by going to the **Pipelines** page in your project.
![Commit status](img/pipelines_status.png)
-Or you can view all builds, by going to the **Pipelines > Builds** page.
+Or you can view all jobs, by going to the **Pipelines ➔ Jobs** page.
![Commit status](img/builds_status.png)
-By clicking on a Build ID, you will be able to see the log of that build.
-This is important to diagnose why a build failed or acted differently than
+By clicking on a job's status, you will be able to see the log of that job.
+This is important to diagnose why a job failed or acted differently than
you expected.
![Build log](img/build_log.png)
You are also able to view the status of any commit in the various pages in
-GitLab, such as **Commits** and **Merge Requests**.
+GitLab, such as **Commits** and **Merge requests**.
## Enabling build emails
If you want to receive e-mail notifications about the result status of the
-builds, you should explicitly enable the **Builds Emails** service under your
+jobs, you should explicitly enable the **Builds Emails** service under your
project's settings.
For more information read the
@@ -224,9 +221,7 @@ For more information read the
Visit the [examples README][examples] to see a list of examples using GitLab
CI with various languages.
-Awesome! You started using CI in GitLab!
-
-[runner-install]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/tree/master#install-gitlab-runner
+[runner-install]: https://docs.gitlab.com/runner/install/
[blog-ci]: https://about.gitlab.com/2015/05/06/why-were-replacing-gitlab-ci-jobs-with-gitlab-ci-dot-yml/
[examples]: ../examples/README.md
[ci]: https://about.gitlab.com/gitlab-ci/
@@ -235,3 +230,4 @@ Awesome! You started using CI in GitLab!
[enabled]: ../enable_or_disable_ci.md
[stages]: ../yaml/README.md#stages
[pipeline]: ../pipelines.md
+[internet]: https://about.gitlab.com/images/theinternet.png
diff --git a/doc/ci/quick_start/img/build_log.png b/doc/ci/quick_start/img/build_log.png
index 87643d62d58..3a7248ca772 100644
--- a/doc/ci/quick_start/img/build_log.png
+++ b/doc/ci/quick_start/img/build_log.png
Binary files differ
diff --git a/doc/ci/quick_start/img/builds_status.png b/doc/ci/quick_start/img/builds_status.png
index d287ae3064f..f829240f3b3 100644
--- a/doc/ci/quick_start/img/builds_status.png
+++ b/doc/ci/quick_start/img/builds_status.png
Binary files differ
diff --git a/doc/ci/quick_start/img/new_commit.png b/doc/ci/quick_start/img/new_commit.png
index 29c2fea5d6d..b3dd848b294 100644
--- a/doc/ci/quick_start/img/new_commit.png
+++ b/doc/ci/quick_start/img/new_commit.png
Binary files differ
diff --git a/doc/ci/quick_start/img/pipelines_status.png b/doc/ci/quick_start/img/pipelines_status.png
index 53ccc49bd66..06d1559f5d2 100644
--- a/doc/ci/quick_start/img/pipelines_status.png
+++ b/doc/ci/quick_start/img/pipelines_status.png
Binary files differ
diff --git a/doc/ci/quick_start/img/runners_activated.png b/doc/ci/quick_start/img/runners_activated.png
index 5ce6fe8e17c..cd83c1a7e4c 100644
--- a/doc/ci/quick_start/img/runners_activated.png
+++ b/doc/ci/quick_start/img/runners_activated.png
Binary files differ
diff --git a/doc/ci/quick_start/img/single_commit_status_pending.png b/doc/ci/quick_start/img/single_commit_status_pending.png
index 91fc9011847..ffc7054d3b0 100644
--- a/doc/ci/quick_start/img/single_commit_status_pending.png
+++ b/doc/ci/quick_start/img/single_commit_status_pending.png
Binary files differ
diff --git a/doc/ci/quick_start/img/status_pending.png b/doc/ci/quick_start/img/status_pending.png
deleted file mode 100644
index cbd44a189d3..00000000000
--- a/doc/ci/quick_start/img/status_pending.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md
index ddebd987650..1bd1ee93ac5 100644
--- a/doc/ci/runners/README.md
+++ b/doc/ci/runners/README.md
@@ -1,11 +1,11 @@
# Runners
In GitLab CI, Runners run your [yaml](../yaml/README.md).
-A runner is an isolated (virtual) machine that picks up builds
+A Runner is an isolated (virtual) machine that picks up jobs
through the coordinator API of GitLab CI.
-A runner can be specific to a certain project or serve any project
-in GitLab CI. A runner that serves all projects is called a shared runner.
+A Runner can be specific to a certain project or serve any project
+in GitLab CI. A Runner that serves all projects is called a shared Runner.
Ideally, GitLab Runner should not be installed on the same machine as GitLab.
Read the [requirements documentation](../../install/requirements.md#gitlab-runner)
@@ -13,150 +13,150 @@ for more information.
## Shared vs. Specific Runners
-A runner that is specific only runs for the specified project. A shared runner
-can run jobs for every project that has enabled the option
-`Allow shared runners`.
+A Runner that is specific only runs for the specified project. A shared Runner
+can run jobs for every project that has enabled the option **Allow shared Runners**.
-**Shared runners** are useful for jobs that have similar requirements,
-between multiple projects. Rather than having multiple runners idling for
-many projects, you can have a single or a small number of runners that handle
-multiple projects. This makes it easier to maintain and update runners.
+**Shared Runners** are useful for jobs that have similar requirements,
+between multiple projects. Rather than having multiple Runners idling for
+many projects, you can have a single or a small number of Runners that handle
+multiple projects. This makes it easier to maintain and update Runners.
-**Specific runners** are useful for jobs that have special requirements or for
+**Specific Runners** are useful for jobs that have special requirements or for
projects with a specific demand. If a job has certain requirements, you can set
-up the specific runner with this in mind, while not having to do this for all
-runners. For example, if you want to deploy a certain project, you can setup
-a specific runner to have the right credentials for this.
+up the specific Runner with this in mind, while not having to do this for all
+Runners. For example, if you want to deploy a certain project, you can setup
+a specific Runner to have the right credentials for this.
-Projects with high demand of CI activity can also benefit from using specific runners.
-By having dedicated runners you are guaranteed that the runner is not being held
+Projects with high demand of CI activity can also benefit from using specific Runners.
+By having dedicated Runners you are guaranteed that the Runner is not being held
up by another project's jobs.
-You can set up a specific runner to be used by multiple projects. The difference
-with a shared runner is that you have to enable each project explicitly for
-the runner to be able to run its jobs.
+You can set up a specific Runner to be used by multiple projects. The difference
+with a shared Runner is that you have to enable each project explicitly for
+the Runner to be able to run its jobs.
-Specific runners do not get shared with forked projects automatically.
+Specific Runners do not get shared with forked projects automatically.
A fork does copy the CI settings (jobs, allow shared, etc) of the cloned repository.
# Creating and Registering a Runner
-There are several ways to create a runner. Only after creation, upon
+There are several ways to create a Runner. Only after creation, upon
registration its status as Shared or Specific is determined.
-[See the documentation for](https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/#installation)
+[See the documentation for](https://docs.gitlab.com/runner/install)
the different methods of installing a Runner instance.
-After installing the runner, you can either register it as `Shared` or as `Specific`.
+After installing the Runner, you can either register it as `Shared` or as `Specific`.
You can only register a Shared Runner if you have admin access to the GitLab instance.
## Registering a Shared Runner
-You can only register a shared runner if you are an admin on the linked
+You can only register a shared Runner if you are an admin on the linked
GitLab instance.
-Grab the shared-runner token on the `admin/runners` page of your GitLab CI
+Grab the shared-Runner token on the `admin/runners` page of your GitLab CI
instance.
![shared token](shared_runner.png)
-Now simply register the runner as any runner:
+Now simply register the Runner as any Runner:
```
sudo gitlab-ci-multi-runner register
```
-Shared runners are enabled by default as of GitLab 8.2, but can be disabled with the
-`DISABLE SHARED RUNNERS` button. Previous versions of GitLab defaulted shared runners to
+Shared Runners are enabled by default as of GitLab 8.2, but can be disabled with the
+`DISABLE SHARED RUNNERS` button. Previous versions of GitLab defaulted shared Runners to
disabled.
## Registering a Specific Runner
Registering a specific can be done in two ways:
-1. Creating a runner with the project registration token
-1. Converting a shared runner into a specific runner (one-way, admin only)
+1. Creating a Runner with the project registration token
+1. Converting a shared Runner into a specific Runner (one-way, admin only)
-There are several ways to create a runner instance. The steps below only
-concern registering the runner on GitLab CI.
+There are several ways to create a Runner instance. The steps below only
+concern registering the Runner on GitLab CI.
### Registering a Specific Runner with a Project Registration token
-To create a specific runner without having admin rights to the GitLab instance,
-visit the project you want to make the runner work for in GitLab CI.
+To create a specific Runner without having admin rights to the GitLab instance,
+visit the project you want to make the Runner work for in GitLab CI.
-Click on the runner tab and use the registration token you find there to
-setup a specific runner for this project.
+Click on the Runner tab and use the registration token you find there to
+setup a specific Runner for this project.
-![project runners in GitLab CI](project_specific.png)
+![project Runners in GitLab CI](project_specific.png)
-To register the runner, run the command below and follow instructions:
+To register the Runner, run the command below and follow instructions:
```
sudo gitlab-ci-multi-runner register
```
-### Lock a specific runner from being enabled for other projects
+### Lock a specific Runner from being enabled for other projects
-You can configure a runner to assign it exclusively to a project. When a
-runner is locked this way, it can no longer be enabled for other projects.
-This setting is available on each runner in *Project Settings* > *Runners*.
+You can configure a Runner to assign it exclusively to a project. When a
+Runner is locked this way, it can no longer be enabled for other projects.
+This setting is available on each Runner in *Project Settings* > *Runners*.
### Making an existing Shared Runner Specific
If you are an admin on your GitLab instance,
-you can make any shared runner a specific runner, _but you can not
-make a specific runner a shared runner_.
+you can make any shared Runner a specific Runner, _but you can not
+make a specific Runner a shared Runner_.
-To make a shared runner specific, go to the runner page (`/admin/runners`)
-and find your runner. Add any projects on the left to make this runner
-run exclusively for these projects, therefore making it a specific runner.
+To make a shared Runner specific, go to the Runner page (`/admin/runners`)
+and find your Runner. Add any projects on the left to make this Runner
+run exclusively for these projects, therefore making it a specific Runner.
-![making a shared runner specific](shared_to_specific_admin.png)
+![making a shared Runner specific](shared_to_specific_admin.png)
## Using Shared Runners Effectively
-If you are planning to use shared runners, there are several things you
+If you are planning to use shared Runners, there are several things you
should keep in mind.
### Use Tags
-You must setup a runner to be able to run all the different types of jobs
+You must setup a Runner to be able to run all the different types of jobs
that it may encounter on the projects it's shared over. This would be
problematic for large amounts of projects, if it wasn't for tags.
By tagging a Runner for the types of jobs it can handle, you can make sure
-shared runners will only run the jobs they are equipped to run.
+shared Runners will only run the jobs they are equipped to run.
-For instance, at GitLab we have runners tagged with "rails" if they contain
+For instance, at GitLab we have Runners tagged with "rails" if they contain
the appropriate dependencies to run Rails test suites.
-### Prevent runner with tags from picking jobs without tags
+### Prevent Runner with tags from picking jobs without tags
-You can configure a runner to prevent it from picking jobs with tags when
-the runner does not have tags assigned. This setting is available on each
-runner in *Project Settings* > *Runners*.
+You can configure a Runner to prevent it from picking jobs with tags when
+the Runner does not have tags assigned. This setting is available on each
+Runner in *Project Settings* > *Runners*.
### Be careful with sensitive information
-If you can run a build on a runner, you can get access to any code it runs
-and get the token of the runner. With shared runners, this means that anyone
-that runs jobs on the runner, can access anyone else's code that runs on the runner.
+If you can run a job on a Runner, you can get access to any code it runs
+and get the token of the Runner. With shared Runners, this means that anyone
+that runs jobs on the Runner, can access anyone else's code that runs on the Runner.
-In addition, because you can get access to the runner token, it is possible
-to create a clone of a runner and submit false builds, for example.
+In addition, because you can get access to the Runner token, it is possible
+to create a clone of a Runner and submit false jobs, for example.
-The above is easily avoided by restricting the usage of shared runners
+The above is easily avoided by restricting the usage of shared Runners
on large public GitLab instances and controlling access to your GitLab instance.
### Forks
Whenever a project is forked, it copies the settings of the jobs that relate
-to it. This means that if you have shared runners setup for a project and
-someone forks that project, the shared runners will also serve jobs of this
+to it. This means that if you have shared Runners setup for a project and
+someone forks that project, the shared Runners will also serve jobs of this
project.
## Attack vectors in Runners
-Mentioned briefly earlier, but the following things of runners can be exploited.
-We're always looking for contributions that can mitigate these [Security Considerations](https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/blob/master/docs/security/index.md).
+Mentioned briefly earlier, but the following things of Runners can be exploited.
+We're always looking for contributions that can mitigate these
+[Security Considerations](https://docs.gitlab.com/runner/security/).
diff --git a/doc/ci/services/mysql.md b/doc/ci/services/mysql.md
index aaf3aa77837..338368dbbc9 100644
--- a/doc/ci/services/mysql.md
+++ b/doc/ci/services/mysql.md
@@ -31,7 +31,7 @@ Database: el_duderino
```
If you are wondering why we used `mysql` for the `Host`, read more at
-[How is service linked to the build](../docker/using_docker_images.md#how-is-service-linked-to-the-build).
+[How is service linked to the job](../docker/using_docker_images.md#how-is-service-linked-to-the-job).
You can also use any other docker image available on [Docker Hub][hub-mysql].
For example, to use MySQL 5.5 the service becomes `mysql:5.5`.
@@ -112,7 +112,7 @@ convenience that runs on [GitLab.com](https://gitlab.com) using our publicly
available [shared runners](../runners/README.md).
Want to hack on it? Simply fork it, commit and push your changes. Within a few
-moments the changes will be picked by a public runner and the build will begin.
+moments the changes will be picked by a public runner and the job will begin.
[hub-mysql]: https://hub.docker.com/r/_/mysql/
[mysql-example-repo]: https://gitlab.com/gitlab-examples/mysql
diff --git a/doc/ci/services/postgres.md b/doc/ci/services/postgres.md
index f787cc0a124..3899b555f32 100644
--- a/doc/ci/services/postgres.md
+++ b/doc/ci/services/postgres.md
@@ -31,7 +31,7 @@ Database: nice_marmot
```
If you are wondering why we used `postgres` for the `Host`, read more at
-[How is service linked to the build](../docker/using_docker_images.md#how-is-service-linked-to-the-build).
+[How is service linked to the job](../docker/using_docker_images.md#how-is-service-linked-to-the-job).
You can also use any other docker image available on [Docker Hub][hub-pg].
For example, to use PostgreSQL 9.3 the service becomes `postgres:9.3`.
@@ -108,7 +108,7 @@ convenience that runs on [GitLab.com](https://gitlab.com) using our publicly
available [shared runners](../runners/README.md).
Want to hack on it? Simply fork it, commit and push your changes. Within a few
-moments the changes will be picked by a public runner and the build will begin.
+moments the changes will be picked by a public runner and the job will begin.
[hub-pg]: https://hub.docker.com/r/_/postgres/
[postgres-example-repo]: https://gitlab.com/gitlab-examples/postgres
diff --git a/doc/ci/services/redis.md b/doc/ci/services/redis.md
index 80705024d2f..554c321fd0c 100644
--- a/doc/ci/services/redis.md
+++ b/doc/ci/services/redis.md
@@ -63,7 +63,7 @@ that runs on [GitLab.com](https://gitlab.com) using our publicly available
[shared runners](../runners/README.md).
Want to hack on it? Simply fork it, commit and push your changes. Within a few
-moments the changes will be picked by a public runner and the build will begin.
+moments the changes will be picked by a public runner and the job will begin.
[hub-redis]: https://hub.docker.com/r/_/redis/
[redis-example-repo]: https://gitlab.com/gitlab-examples/redis
diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md
index b858029d25e..49e7ac38b26 100644
--- a/doc/ci/ssh_keys/README.md
+++ b/doc/ci/ssh_keys/README.md
@@ -25,7 +25,7 @@ This is the universal solution which works with any type of executor
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 build to load the private key.
+3. Run the [ssh-agent][] during job to load the private key.
## SSH keys when using the Docker executor
@@ -77,7 +77,7 @@ 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 builds.
+First, you need to login to the server that runs your jobs.
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).
@@ -103,7 +103,7 @@ that runs on [GitLab.com](https://gitlab.com) using our publicly available
[shared runners](../runners/README.md).
Want to hack on it? Simply fork it, commit and push your changes. Within a few
-moments the changes will be picked by a public runner and the build will begin.
+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
diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md
index efca05af7b8..740edba1f59 100644
--- a/doc/ci/triggers/README.md
+++ b/doc/ci/triggers/README.md
@@ -1,19 +1,19 @@
-# Triggering Builds through the API
-
-> [Introduced][ci-229] in GitLab CE 7.14.
+# Triggering jobs through the API
> **Note**:
-GitLab 8.12 has a completely redesigned build permissions system.
-Read all about the [new model and its implications](../../user/project/new_ci_build_permissions_model.md#build-triggers).
+- [Introduced][ci-229] in GitLab CE 7.14.
+- GitLab 8.12 has a completely redesigned job permissions system. Read all
+ about the [new model and its implications](../../user/project/new_ci_build_permissions_model.md#job-triggers).
Triggers can be used to force a rebuild of a specific `ref` (branch or tag)
with an API call.
## Add a trigger
-You can add a new trigger by going to your project's **Settings > Triggers**.
-The **Add trigger** button will create a new token which you can then use to
-trigger a rebuild of this particular project.
+You can add a new trigger by going to your project's
+**Settings ➔ CI/CD Pipelines ➔ Triggers**. The **Add trigger** button will
+create a new token which you can then use to trigger a rerun of this
+particular project's pipeline.
Every new trigger you create, gets assigned a different token which you can
then use inside your scripts or `.gitlab-ci.yml`. You also have a nice
@@ -27,13 +27,13 @@ You can revoke a trigger any time by going at your project's
**Settings > Triggers** and hitting the **Revoke** button. The action is
irreversible.
-## Trigger a build
+## Trigger a job
> **Note**:
Valid refs are only the branches and tags. If you pass a commit SHA as a ref,
-it will not trigger a build.
+it will not trigger a job.
-To trigger a build you need to send a `POST` request to GitLab's API endpoint:
+To trigger a job you need to send a `POST` request to GitLab's API endpoint:
```
POST /projects/:id/trigger/builds
@@ -42,31 +42,32 @@ POST /projects/:id/trigger/builds
The required parameters are the trigger's `token` and the Git `ref` on which
the trigger will be performed. Valid refs are the branch and the tag. The `:id`
of a project can be found by [querying the API](../../api/projects.md)
-or by visiting the **Triggers** page which provides self-explanatory examples.
+or by visiting the **CI/CD Pipelines** settings page which provides
+self-explanatory examples.
-When a rebuild is triggered, the information is exposed in GitLab's UI under
-the **Builds** page and the builds are marked as `triggered`.
+When a rerun of a pipeline is triggered, the information is exposed in GitLab's
+UI under the **Jobs** page and the jobs are marked as triggered 'by API'.
-![Marked rebuilds as triggered on builds page](img/builds_page.png)
+![Marked rebuilds as on jobs page](img/builds_page.png)
---
-You can see which trigger caused the rebuild by visiting the single build page.
-The token of the trigger is exposed in the UI as you can see from the image
+You can see which trigger caused the rebuild by visiting the single job page.
+A part of the trigger's token is exposed in the UI as you can see from the image
below.
-![Marked rebuilds as triggered on a single build page](img/trigger_single_build.png)
+![Marked rebuilds as triggered on a single job page](img/trigger_single_build.png)
---
See the [Examples](#examples) section for more details on how to actually
trigger a rebuild.
-## Trigger a build from webhook
+## Trigger a job from webhook
> Introduced in GitLab 8.14.
-To trigger a build from webhook of another project you need to add the following
+To trigger a job from webhook of another project you need to add the following
webhook url for Push and Tag push events:
```
@@ -78,7 +79,7 @@ https://gitlab.example.com/api/v3/projects/:id/ref/:ref/trigger/builds?token=TOK
from webhook body that designates the branchref that fired the trigger in the source repository.
- `ref` should be url encoded if contains slashes.
-## Pass build variables to a trigger
+## Pass job variables to a trigger
You can pass any number of arbitrary variables in the trigger API call and they
will be available in GitLab CI so that they can be used in your `.gitlab-ci.yml`
@@ -90,7 +91,7 @@ variables[key]=value
This information is also exposed in the UI.
-![Build variables in UI](img/trigger_variables.png)
+![Job variables in UI](img/trigger_variables.png)
---
@@ -116,7 +117,7 @@ curl --request POST \
"https://gitlab.example.com/api/v3/projects/9/trigger/builds?token=TOKEN&ref=master"
```
-### Triggering a build within `.gitlab-ci.yml`
+### Triggering a job within `.gitlab-ci.yml`
You can also benefit by using triggers in your `.gitlab-ci.yml`. Let's say that
you have two projects, A and B, and you want to trigger a rebuild on the `master`
@@ -132,7 +133,7 @@ build_docs:
- tags
```
-Now, whenever a new tag is pushed on project A, the build will run and the
+Now, whenever a new tag is pushed on project A, the job will run and the
`build_docs` job will be executed, triggering a rebuild of project B. The
`stage: deploy` ensures that this job will run only after all jobs with
`stage: test` complete successfully.
@@ -189,18 +190,18 @@ curl --request POST \
https://gitlab.example.com/api/v3/projects/9/trigger/builds
```
-### Using webhook to trigger builds
+### Using webhook to trigger job
-You can add the following webhook to another project in order to trigger a build:
+You can add the following webhook to another project in order to trigger a job:
```
https://gitlab.example.com/api/v3/projects/9/ref/master/trigger/builds?token=TOKEN&variables[UPLOAD_TO_S3]=true
```
-### Using cron to trigger nightly builds
+### Using cron to trigger nightly jobs
-Whether you craft a script or just run cURL directly, you can trigger builds
-in conjunction with cron. The example below triggers a build on the `master`
+Whether you craft a script or just run cURL directly, you can trigger jobs
+in conjunction with cron. The example below triggers a job on the `master`
branch of project with ID `9` every night at `00:30`:
```bash
diff --git a/doc/ci/triggers/img/builds_page.png b/doc/ci/triggers/img/builds_page.png
index fded5839f76..c9cc8f308f4 100644
--- a/doc/ci/triggers/img/builds_page.png
+++ b/doc/ci/triggers/img/builds_page.png
Binary files differ
diff --git a/doc/ci/triggers/img/trigger_single_build.png b/doc/ci/triggers/img/trigger_single_build.png
index c4a5550d640..837bbeffe9f 100644
--- a/doc/ci/triggers/img/trigger_single_build.png
+++ b/doc/ci/triggers/img/trigger_single_build.png
Binary files differ
diff --git a/doc/ci/triggers/img/trigger_variables.png b/doc/ci/triggers/img/trigger_variables.png
index 65fe1ea9ab6..0c2a761cfa9 100644
--- a/doc/ci/triggers/img/trigger_variables.png
+++ b/doc/ci/triggers/img/trigger_variables.png
Binary files differ
diff --git a/doc/ci/triggers/img/triggers_page.png b/doc/ci/triggers/img/triggers_page.png
index 56d13905ce6..8ebf68d0384 100644
--- a/doc/ci/triggers/img/triggers_page.png
+++ b/doc/ci/triggers/img/triggers_page.png
Binary files differ
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 49fca884f35..620d4744685 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -1,6 +1,6 @@
# Variables
-When receiving a build from GitLab CI, the [Runner] prepares the build environment.
+When receiving a job from GitLab CI, the [Runner] prepares the build environment.
It starts by setting a list of **predefined variables** (environment variables)
and a list of **user-defined variables**.
@@ -29,22 +29,22 @@ version of Runner required.
| Variable | GitLab | Runner | Description |
|-------------------------|--------|--------|-------------|
-| **CI** | all | 0.4 | Mark that build is executed in CI environment |
-| **GITLAB_CI** | all | all | Mark that build is executed in GitLab CI environment |
-| **CI_SERVER** | all | all | Mark that build is executed in CI environment |
-| **CI_SERVER_NAME** | all | all | The name of CI server that is used to coordinate builds |
-| **CI_SERVER_VERSION** | all | all | GitLab version that is used to schedule builds |
-| **CI_SERVER_REVISION** | all | all | GitLab revision that is used to schedule builds |
-| **CI_BUILD_ID** | all | all | The unique id of the current build that GitLab CI uses internally |
+| **CI** | all | 0.4 | Mark that job is executed in CI environment |
+| **GITLAB_CI** | all | all | Mark that job is executed in GitLab CI environment |
+| **CI_SERVER** | all | all | Mark that job is executed in CI environment |
+| **CI_SERVER_NAME** | all | all | The name of CI server that is used to coordinate jobs |
+| **CI_SERVER_VERSION** | all | all | GitLab version that is used to schedule jobs |
+| **CI_SERVER_REVISION** | all | all | GitLab revision that is used to schedule jobs |
+| **CI_BUILD_ID** | all | all | The unique id of the current job that GitLab CI uses internally |
| **CI_BUILD_REF** | all | all | The commit revision for which project is built |
| **CI_BUILD_TAG** | all | 0.5 | The commit tag name. Present only when building tags. |
-| **CI_BUILD_NAME** | all | 0.5 | The name of the build as defined in `.gitlab-ci.yml` |
+| **CI_BUILD_NAME** | all | 0.5 | The name of the job as defined in `.gitlab-ci.yml` |
| **CI_BUILD_STAGE** | all | 0.5 | The name of the stage as defined in `.gitlab-ci.yml` |
| **CI_BUILD_REF_NAME** | all | all | The branch or tag name for which project is built |
| **CI_BUILD_REF_SLUG** | 8.15 | all | `$CI_BUILD_REF_NAME` lowercased, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. Use in URLs and domain names. |
| **CI_BUILD_REPO** | all | all | The URL to clone the Git repository |
-| **CI_BUILD_TRIGGERED** | all | 0.5 | The flag to indicate that build was [triggered] |
-| **CI_BUILD_MANUAL** | 8.12 | all | The flag to indicate that build was manually started |
+| **CI_BUILD_TRIGGERED** | all | 0.5 | The flag to indicate that job was [triggered] |
+| **CI_BUILD_MANUAL** | 8.12 | all | The flag to indicate that job was manually started |
| **CI_BUILD_TOKEN** | all | 1.2 | Token used for authenticating with the GitLab Container Registry |
| **CI_PIPELINE_ID** | 8.10 | 0.5 | The unique id of the current pipeline that GitLab CI uses internally |
| **CI_PROJECT_ID** | all | all | The unique id of the current project that GitLab CI uses internally |
@@ -52,8 +52,8 @@ version of Runner required.
| **CI_PROJECT_NAMESPACE**| 8.10 | 0.5 | The project namespace (username or groupname) that is currently being built |
| **CI_PROJECT_PATH** | 8.10 | 0.5 | The namespace with project name |
| **CI_PROJECT_URL** | 8.10 | 0.5 | The HTTP address to access project |
-| **CI_PROJECT_DIR** | all | all | The full path where the repository is cloned and where the build is run |
-| **CI_ENVIRONMENT_NAME** | 8.15 | all | The name of the environment for this build |
+| **CI_PROJECT_DIR** | all | all | The full path where the repository is cloned and where the job is run |
+| **CI_ENVIRONMENT_NAME** | 8.15 | all | The name of the environment for this job |
| **CI_ENVIRONMENT_SLUG** | 8.15 | all | A simplified version of the environment name, suitable for inclusion in DNS, URLs, Kubernetes labels, etc. |
| **CI_REGISTRY** | 8.10 | 0.5 | If the Container Registry is enabled it returns the address of GitLab's Container Registry |
| **CI_REGISTRY_IMAGE** | 8.10 | 0.5 | If the Container Registry is enabled for the project it returns the address of the registry tied to the specific project |
@@ -61,11 +61,11 @@ version of Runner required.
| **CI_RUNNER_DESCRIPTION** | 8.10 | 0.5 | The description of the runner as saved in GitLab |
| **CI_RUNNER_TAGS** | 8.10 | 0.5 | The defined runner tags |
| **CI_DEBUG_TRACE** | all | 1.7 | Whether [debug tracing](#debug-tracing) is enabled |
-| **GET_SOURCES_ATTEMPTS** | 8.15 | 1.9 | Number of attempts to fetch sources running a build |
-| **ARTIFACT_DOWNLOAD_ATTEMPTS** | 8.15 | 1.9 | Number of attempts to download artifacts running a build |
-| **RESTORE_CACHE_ATTEMPTS** | 8.15 | 1.9 | Number of attempts to restore the cache running a build |
-| **GITLAB_USER_ID** | 8.12 | all | The id of the user who started the build |
-| **GITLAB_USER_EMAIL** | 8.12 | all | The email of the user who started the build |
+| **GET_SOURCES_ATTEMPTS** | 8.15 | 1.9 | Number of attempts to fetch sources running a job |
+| **ARTIFACT_DOWNLOAD_ATTEMPTS** | 8.15 | 1.9 | Number of attempts to download artifacts running a job |
+| **RESTORE_CACHE_ATTEMPTS** | 8.15 | 1.9 | Number of attempts to restore the cache running a job |
+| **GITLAB_USER_ID** | 8.12 | all | The id of the user who started the job |
+| **GITLAB_USER_EMAIL** | 8.12 | all | The email of the user who started the job |
Example values:
@@ -136,7 +136,7 @@ job_name:
>**Notes:**
- This feature requires GitLab Runner 0.4.0 or higher.
- Be aware that secret variables are not masked, and their values can be shown
- in the build logs if explicitly asked to do so. If your project is public or
+ in the job logs if explicitly asked to do so. If your project is public or
internal, you can set the pipelines private from your project's Pipelines
settings. Follow the discussion in issue [#13784][ce-13784] for masking the
secret variables.
@@ -148,9 +148,10 @@ available in the build environment. It's the recommended method to use for
storing things like passwords, secret keys and credentials.
Secret variables can be added by going to your project's
-**Settings ➔ Variables ➔ Add variable**.
+**Settings ➔ CI/CD Pipelines**, then finding the section called
+**Secret Variables**.
-Once you set them, they will be available for all subsequent builds.
+Once you set them, they will be available for all subsequent jobs.
## Deployment variables
@@ -160,7 +161,7 @@ This feature requires GitLab CI 8.15 or higher.
[Project services](../../user/project/integrations/project_services.md) that are
responsible for deployment configuration may define their own variables that
are set in the build environment. These variables are only defined for
-[deployment builds](../environments.md). Please consult the documentation of
+[deployment jobs](../environments.md). Please consult the documentation of
the project services that you are using to learn which variables they define.
An example project service that defines deployment variables is
@@ -173,21 +174,21 @@ An example project service that defines deployment variables is
> **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 build traces!
+ in job traces!
By default, GitLab Runner hides most of the details of what it is doing when
-processing a job. This behaviour keeps build traces short, and prevents secrets
+processing a job. This behaviour 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
investigate; in these cases, you can enable debug tracing in `.gitlab-ci.yml`.
Available on GitLab Runner v1.7+, this feature enables the shell's execution
-trace, resulting in a verbose build trace listing all commands that were run,
+trace, resulting in a verbose job trace listing all commands that were run,
variables that were set, etc.
-Before enabling this, you should ensure builds are visible to
+Before enabling this, you should ensure jobs are visible to
[team members only](../../user/permissions.md#project-features). You should
-also [erase](../pipelines.md#seeing-build-status) all generated build traces
+also [erase](../pipelines.md#seeing-build-status) all generated job traces
before making them visible again.
To enable debug traces, set the `CI_DEBUG_TRACE` variable to `true`:
@@ -320,7 +321,7 @@ MIIFQzCCBCugAwIBAgIRAL/ElDjuf15xwja1ZnCocWAwDQYJKoZIhvcNAQELBQAw'
All variables are set as environment variables in the build environment, and
they are accessible with normal methods that are used to access such variables.
-In most cases `bash` or `sh` is used to execute the build script.
+In most cases `bash` or `sh` is used to execute the job script.
To access the variables (predefined and user-defined) in a `bash`/`sh` environment,
prefix the variable name with the dollar sign (`$`):
@@ -328,12 +329,12 @@ prefix the variable name with the dollar sign (`$`):
```
job_name:
script:
- - echo $CI_BUILD_ID
+ - echo $CI_job_ID
```
You can also list all environment variables with the `export` command,
but be aware that this will also expose the values of all the secret variables
-you set, in the build log:
+you set, in the job log:
```
job_name:
@@ -344,4 +345,4 @@ job_name:
[ce-13784]: https://gitlab.com/gitlab-org/gitlab-ce/issues/13784
[runner]: https://docs.gitlab.com/runner/
[triggered]: ../triggers/README.md
-[triggers]: ../triggers/README.md#pass-build-variables-to-a-trigger
+[triggers]: ../triggers/README.md#pass-job-variables-to-a-trigger
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index cd492d16747..dd3ba1283f8 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -1,7 +1,7 @@
-# Configuration of your builds with .gitlab-ci.yml
+# Configuration of your jobs with .gitlab-ci.yml
This document describes the usage of `.gitlab-ci.yml`, the file that is used by
-GitLab Runner to manage your project's builds.
+GitLab Runner to manage your project's jobs.
If you want a quick introduction to GitLab CI, follow our
[quick start guide](../quick_start/README.md).
@@ -30,10 +30,9 @@ jobs, where each of the jobs executes a different command.
Of course a command can execute code directly (`./configure;make;make install`)
or run a script (`test.sh`) in the repository.
-Jobs are used to create builds, which are then picked up by
-[Runners](../runners/README.md) and executed within the environment of the
-Runner. What is important, is that each job is run independently from each
-other.
+Jobs are picked up by [Runners](../runners/README.md) and executed within the
+environment of the Runner. What is important, is that each job is run
+independently from each other.
The YAML syntax allows for using more complex job specifications than in the
above example:
@@ -80,30 +79,31 @@ There are a few reserved `keywords` that **cannot** be used as job names:
### image and services
This allows to specify a custom Docker image and a list of services that can be
-used for time of the build. The configuration of this feature is covered in
+used for time of the job. The configuration of this feature is covered in
[a separate document](../docker/README.md).
### before_script
`before_script` is used to define the command that should be run before all
-builds, including deploy builds, but after the restoration of artifacts. This can be an array or a multi-line string.
+jobs, including deploy jobs, but after the restoration of artifacts. This can
+be an array or a multi-line string.
### after_script
> Introduced in GitLab 8.7 and requires Gitlab Runner v1.2
`after_script` is used to define the command that will be run after for all
-builds. This has to be an array or a multi-line string.
+jobs. This has to be an array or a multi-line string.
### stages
-`stages` is used to define build stages that can be used by jobs.
+`stages` is used to define stages that can be used by jobs.
The specification of `stages` allows for having flexible multi stage pipelines.
-The ordering of elements in `stages` defines the ordering of builds' execution:
+The ordering of elements in `stages` defines the ordering of jobs' execution:
-1. Builds of the same stage are run in parallel.
-1. Builds of the next stage are run after the jobs from the previous stage
+1. Jobs of the same stage are run in parallel.
+1. Jobs of the next stage are run after the jobs from the previous stage
complete successfully.
Let's consider the following example, which defines 3 stages:
@@ -115,7 +115,7 @@ stages:
- deploy
```
-1. First all jobs of `build` are executed in parallel.
+1. First, all jobs of `build` are executed in parallel.
1. If all jobs of `build` succeed, the `test` jobs are executed in parallel.
1. If all jobs of `test` succeed, the `deploy` jobs are executed in parallel.
1. If all jobs of `deploy` succeed, the commit is marked as `success`.
@@ -124,7 +124,7 @@ stages:
There are also two edge cases worth mentioning:
-1. If no `stages` are defined in `.gitlab-ci.yml`, then by default the `build`,
+1. If no `stages` are defined in `.gitlab-ci.yml`, then the `build`,
`test` and `deploy` are allowed to be used as job's stage by default.
2. If a job doesn't specify a `stage`, the job is assigned the `test` stage.
@@ -137,7 +137,7 @@ Alias for [stages](#stages).
> Introduced in GitLab Runner v0.5.0.
GitLab CI allows you to add variables to `.gitlab-ci.yml` that are set in the
-build environment. The variables are stored in the Git repository and are meant
+job environment. The variables are stored in the Git repository and are meant
to store non-sensitive project configuration, for example:
```yaml
@@ -163,7 +163,7 @@ which can be set in GitLab's UI.
> Introduced in GitLab Runner v0.7.0.
`cache` is used to specify a list of files and directories which should be
-cached between builds. You can only use paths that are within the project
+cached between jobs. You can only use paths that are within the project
workspace.
**By default the caching is enabled per-job and per-branch.**
@@ -202,8 +202,8 @@ rspec:
- binaries/
```
-Locally defined cache overwrites globally defined options. This will cache only
-`binaries/`:
+Locally defined cache overwrites globally defined options. The following `rspec`
+job will cache only `binaries/`:
```yaml
cache:
@@ -281,8 +281,8 @@ cache:
## Jobs
`.gitlab-ci.yml` allows you to specify an unlimited number of jobs. Each job
-must have a unique name, which is not one of the Keywords mentioned above.
-A job is defined by a list of parameters that define the build behavior.
+must have a unique name, which is not one of the keywords mentioned above.
+A job is defined by a list of parameters that define the job behavior.
```yaml
job_name:
@@ -302,24 +302,24 @@ job_name:
| Keyword | Required | Description |
|---------------|----------|-------------|
-| script | yes | Defines a shell script which is executed by Runner |
-| image | no | Use docker image, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) |
-| services | no | Use docker services, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) |
-| stage | no | Defines a build stage (default: `test`) |
-| type | no | Alias for `stage` |
-| variables | no | Define build variables on a job level |
-| only | no | Defines a list of git refs for which build is created |
-| except | no | Defines a list of git refs for which build is not created |
-| tags | no | Defines a list of tags which are used to select Runner |
-| allow_failure | no | Allow build to fail. Failed build doesn't contribute to commit status |
-| when | no | Define when to run build. Can be `on_success`, `on_failure`, `always` or `manual` |
-| dependencies | no | Define other builds that a build depends on so that you can pass artifacts between them|
-| artifacts | no | Define list of build artifacts |
-| cache | no | Define list of files that should be cached between subsequent runs |
-| before_script | no | Override a set of commands that are executed before build |
-| after_script | no | Override a set of commands that are executed after build |
-| environment | no | Defines a name of environment to which deployment is done by this build |
-| coverage | no | Define code coverage settings for a given job |
+| script | yes | Defines a shell script which is executed by Runner |
+| image | no | Use docker image, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) |
+| services | no | Use docker services, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) |
+| stage | no | Defines a job stage (default: `test`) |
+| type | no | Alias for `stage` |
+| variables | no | Define job variables on a job level |
+| only | no | Defines a list of git refs for which job is created |
+| except | no | Defines a list of git refs for which job is not created |
+| tags | no | Defines a list of tags which are used to select Runner |
+| allow_failure | no | Allow job to fail. Failed job doesn't contribute to commit status |
+| when | no | Define when to run job. Can be `on_success`, `on_failure`, `always` or `manual` |
+| dependencies | no | Define other jobs that a job depends on so that you can pass artifacts between them|
+| artifacts | no | Define list of [job artifacts](../../user/project/pipelines/job_artifacts.md) |
+| cache | no | Define list of files that should be cached between subsequent runs |
+| before_script | no | Override a set of commands that are executed before job |
+| after_script | no | Override a set of commands that are executed after job |
+| environment | no | Defines a name of environment to which deployment is done by this job |
+| coverage | no | Define code coverage settings for a given job |
### script
@@ -339,11 +339,15 @@ job:
- bundle exec rspec
```
-Sometimes, `script` commands will need to be wrapped in single or double quotes. For example, commands that contain a colon (`:`) need to be wrapped in quotes so that the YAML parser knows to interpret the whole thing as a string rather than a "key: value" pair. Be careful when using special characters (`:`, `{`, `}`, `[`, `]`, `,`, `&`, `*`, `#`, `?`, `|`, `-`, `<`, `>`, `=`, `!`, `%`, `@`, `` ` ``).
+Sometimes, `script` commands will need to be wrapped in single or double quotes.
+For example, commands that contain a colon (`:`) need to be wrapped in quotes so
+that the YAML parser knows to interpret the whole thing as a string rather than
+a "key: value" pair. Be careful when using special characters:
+`:`, `{`, `}`, `[`, `]`, `,`, `&`, `*`, `#`, `?`, `|`, `-`, `<`, `>`, `=`, `!`, `%`, `@`, `` ` ``.
### stage
-`stage` allows to group build into different stages. Builds of the same `stage`
+`stage` allows to group jobs into different stages. Jobs of the same `stage`
are executed in `parallel`. For more info about the use of `stage` please check
[stages](#stages).
@@ -352,10 +356,9 @@ are executed in `parallel`. For more info about the use of `stage` please check
`only` and `except` are two parameters that set a refs policy to limit when
jobs are built:
-1. `only` defines the names of branches and tags for which the job will be
- built.
+1. `only` defines the names of branches and tags for which the job will run.
2. `except` defines the names of branches and tags for which the job will
- **not** be built.
+ **not** run.
There are a few rules that apply to the usage of refs policy:
@@ -379,8 +382,8 @@ job:
- branches
```
-In this example, `job` will run only for refs that are tagged, or if a build is explicitly requested
-via an API trigger.
+In this example, `job` will run only for refs that are tagged, or if a build is
+explicitly requested via an API trigger.
```yaml
job:
@@ -404,14 +407,14 @@ job:
The above example will run `job` for all branches on `gitlab-org/gitlab-ce`,
except master.
-### job variables
+### Job variables
-It is possible to define build variables using a `variables` keyword on a job
-level. It works basically the same way as its [global-level equivalent](#variables)
-but allows you to define job-specific build variables.
+It is possible to define job variables using a `variables` keyword on a job
+level. It works basically the same way as its [global-level equivalent](#variables),
+but allows you to define job-specific variables.
-When the `variables` keyword is used on a job level, it overrides global YAML
-build variables and predefined variables. To turn off global defined variables
+When the `variables` keyword is used on a job level, it overrides the global YAML
+job variables and predefined ones. To turn off global defined variables
in your job, define an empty array:
```yaml
@@ -419,8 +422,7 @@ job_name:
variables: []
```
-Build variables priority is defined in the
-[variables documentation][variables].
+Job variables priority is defined in the [variables documentation][variables].
### tags
@@ -430,7 +432,7 @@ allowed to run this project.
During the registration of a Runner, you can specify the Runner's tags, for
example `ruby`, `postgres`, `development`.
-`tags` allow you to run builds with Runners that have the specified tags
+`tags` allow you to run jobs with Runners that have the specified tags
assigned to them:
```yaml
@@ -445,13 +447,13 @@ has both `ruby` AND `postgres` tags defined.
### allow_failure
-`allow_failure` is used when you want to allow a build to fail without impacting
-the rest of the CI suite. Failed builds don't contribute to the commit status.
+`allow_failure` is used when you want to allow a job to fail without impacting
+the rest of the CI suite. Failed jobs don't contribute to the commit status.
-When enabled and the build fails, the pipeline will be successful/green for all
+When enabled and the job fails, the pipeline will be successful/green for all
intents and purposes, but a "CI build passed with warnings" message will be
-displayed on the merge request or commit or build page. This is to be used by
-builds that are allowed to fail, but where failure indicates some other (manual)
+displayed on the merge request or commit or job page. This is to be used by
+jobs that are allowed to fail, but where failure indicates some other (manual)
steps should be taken elsewhere.
In the example below, `job1` and `job2` will run in parallel, but if `job1`
@@ -483,12 +485,12 @@ failure.
`when` can be set to one of the following values:
-1. `on_success` - execute build only when all builds from prior stages
+1. `on_success` - execute job only when all jobs from prior stages
succeed. This is the default.
-1. `on_failure` - execute build only when at least one build from prior stages
+1. `on_failure` - execute job only when at least one job from prior stages
fails.
-1. `always` - execute build regardless of the status of builds from prior stages.
-1. `manual` - execute build manually (added in GitLab 8.10). Read about
+1. `always` - execute job regardless of the status of jobs from prior stages.
+1. `manual` - execute job manually (added in GitLab 8.10). Read about
[manual actions](#manual-actions) below.
For example:
@@ -526,7 +528,7 @@ deploy_job:
cleanup_job:
stage: cleanup
script:
- - cleanup after builds
+ - cleanup after jobs
when: always
```
@@ -552,10 +554,11 @@ Read more at the [environments documentation][env-manual].
### environment
-> Introduced in GitLab 8.9.
-
-> You can read more about environments and find more examples in the
-[documentation about environments][environment].
+>
+**Notes:**
+- Introduced in GitLab 8.9.
+- You can read more about environments and find more examples in the
+ [documentation about environments][environment].
`environment` is used to define that a job deploys to a specific environment.
If `environment` is specified and no environment under that name exists, a new
@@ -563,7 +566,7 @@ one will be created automatically.
In its simplest form, the `environment` keyword can be defined like:
-```
+```yaml
deploy to production:
stage: deploy
script: git push production HEAD:master
@@ -576,12 +579,12 @@ deployment to the `production` environment.
#### environment:name
-> Introduced in GitLab 8.11.
-
->**Note:**
-Before GitLab 8.11, the name of an environment could be defined as a string like
-`environment: production`. The recommended way now is to define it under the
-`name` keyword.
+>
+**Notes:**
+- Introduced in GitLab 8.11.
+- Before GitLab 8.11, the name of an environment could be defined as a string like
+ `environment: production`. The recommended way now is to define it under the
+ `name` keyword.
The `environment` name can contain:
@@ -602,7 +605,7 @@ Instead of defining the name of the environment right after the `environment`
keyword, it is also possible to define it as a separate value. For that, use
the `name` keyword under `environment`:
-```
+```yaml
deploy to production:
stage: deploy
script: git push production HEAD:master
@@ -612,11 +615,11 @@ deploy to production:
#### environment:url
-> Introduced in GitLab 8.11.
-
->**Note:**
-Before GitLab 8.11, the URL could be added only in GitLab's UI. The
-recommended way now is to define it in `.gitlab-ci.yml`.
+>
+**Notes:**
+- Introduced in GitLab 8.11.
+- Before GitLab 8.11, the URL could be added only in GitLab's UI. The
+ recommended way now is to define it in `.gitlab-ci.yml`.
This is an optional value that when set, it exposes buttons in various places
in GitLab which when clicked take you to the defined URL.
@@ -625,7 +628,7 @@ In the example below, if the job finishes successfully, it will create buttons
in the merge requests and in the environments/deployments pages which will point
to `https://prod.example.com`.
-```
+```yaml
deploy to production:
stage: deploy
script: git push production HEAD:master
@@ -687,11 +690,15 @@ The `stop_review_app` job is **required** to have the following keywords defined
- `when` - [reference](#when)
- `environment:name`
- `environment:action`
+- `stage` should be the same as the `review_app` in order for the environment
+ to stop automatically when the branch is deleted
#### dynamic environments
-> [Introduced][ce-6323] in GitLab 8.12 and GitLab Runner 1.6.
- `$CI_ENVIRONMENT_SLUG` was [introduced][ce-7983] in GitLab 8.15
+>
+**Notes:**
+- [Introduced][ce-6323] in GitLab 8.12 and GitLab Runner 1.6.
+- The `$CI_ENVIRONMENT_SLUG` was [introduced][ce-7983] in GitLab 8.15.
`environment` can also represent a configuration hash with `name` and `url`.
These parameters can use any of the defined [CI variables](#variables)
@@ -699,7 +706,7 @@ These parameters can use any of the defined [CI variables](#variables)
For example:
-```
+```yaml
deploy as review app:
stage: deploy
script: make deploy
@@ -714,28 +721,27 @@ is an [environment variable][variables] set by the Runner. The
`$CI_ENVIRONMENT_SLUG` variable is based on the environment name, but suitable
for inclusion in URLs. In this case, if the `deploy as review app` job was run
in a branch named `pow`, this environment would be accessible with an URL like
-`https://review-pow-aaaaaa.example.com/`.
+`https://review-pow.example.com/`.
This of course implies that the underlying server which hosts the application
is properly configured.
The common use case is to create dynamic environments for branches and use them
as Review Apps. You can see a simple example using Review Apps at
-https://gitlab.com/gitlab-examples/review-apps-nginx/.
+<https://gitlab.com/gitlab-examples/review-apps-nginx/>.
### artifacts
->**Notes:**
>
-> - Introduced in GitLab Runner v0.7.0 for non-Windows platforms.
-> - Windows support was added in GitLab Runner v.1.0.0.
-> - Currently not all executors are supported.
-> - Build artifacts are only collected for successful builds by default.
+**Notes:**
+- Introduced in GitLab Runner v0.7.0 for non-Windows platforms.
+- Windows support was added in GitLab Runner v.1.0.0.
+- Currently not all executors are supported.
+- Job artifacts are only collected for successful jobs by default.
`artifacts` is used to specify a list of files and directories which should be
-attached to the build after success. You can only use paths that are within the
-project workspace. To pass artifacts between different builds, see [dependencies](#dependencies).
-
+attached to the job after success. You can only use paths that are within the
+project workspace. To pass artifacts between different jobs, see [dependencies](#dependencies).
Below are some examples.
Send all files in `binaries` and `.config`:
@@ -794,7 +800,7 @@ release-job:
- tags
```
-The artifacts will be sent to GitLab after a successful build and will
+The artifacts will be sent to GitLab after the job finishes successfully and will
be available for download in the GitLab UI.
#### artifacts:name
@@ -811,7 +817,7 @@ The default name is `artifacts`, which becomes `artifacts.zip` when downloaded.
**Example configurations**
-To create an archive with a name of the current build:
+To create an archive with a name of the current job:
```yaml
job:
@@ -829,7 +835,7 @@ job:
untracked: true
```
-To create an archive with a name of the current build and the current branch or
+To create an archive with a name of the current job and the current branch or
tag including only the files that are untracked by Git:
```yaml
@@ -864,20 +870,20 @@ job:
> Introduced in GitLab 8.9 and GitLab Runner v1.3.0.
-`artifacts:when` is used to upload artifacts on build failure or despite the
+`artifacts:when` is used to upload artifacts on job failure or despite the
failure.
`artifacts:when` can be set to one of the following values:
-1. `on_success` - upload artifacts only when the build succeeds. This is the default.
-1. `on_failure` - upload artifacts only when the build fails.
-1. `always` - upload artifacts regardless of the build status.
+1. `on_success` - upload artifacts only when the job succeeds. This is the default.
+1. `on_failure` - upload artifacts only when the job fails.
+1. `always` - upload artifacts regardless of the job status.
---
**Example configurations**
-To upload artifacts only when build fails.
+To upload artifacts only when job fails.
```yaml
job:
@@ -894,13 +900,14 @@ time. By default, artifacts are stored on GitLab forever. `expire_in` allows you
to specify how long artifacts should live before they expire, counting from the
time they are uploaded and stored on GitLab.
-You can use the **Keep** button on the build page to override expiration and
+You can use the **Keep** button on the job page to override expiration and
keep artifacts forever.
After expiry, artifacts are actually deleted hourly by default (via a cron job),
but they are not accessible after expiry.
The value of `expire_in` is an elapsed time. Examples of parseable values:
+
- '3 mins 4 sec'
- '2 hrs 20 min'
- '2h20min'
@@ -925,14 +932,14 @@ job:
> Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
This feature should be used in conjunction with [`artifacts`](#artifacts) and
-allows you to define the artifacts to pass between different builds.
+allows you to define the artifacts to pass between different jobs.
Note that `artifacts` from all previous [stages](#stages) are passed by default.
To use this feature, define `dependencies` in context of the job and pass
-a list of all previous builds from which the artifacts should be downloaded.
-You can only define builds from stages that are executed before the current one.
-An error will be shown if you define builds from the current stage or next ones.
+a list of all previous jobs from which the artifacts should be downloaded.
+You can only define jobs from stages that are executed before the current one.
+An error will be shown if you define jobs from the current stage or next ones.
Defining an empty array will skip downloading any artifacts for that job.
---
@@ -942,7 +949,7 @@ In the following example, we define two jobs with artifacts, `build:osx` and
will be downloaded and extracted in the context of the build. The same happens
for `test:linux` and artifacts from `build:linux`.
-The job `deploy` will download artifacts from all previous builds because of
+The job `deploy` will download artifacts from all previous jobs because of
the [stage](#stages) precedence:
```yaml
@@ -979,7 +986,7 @@ deploy:
### before_script and after_script
-It's possible to overwrite globally defined `before_script` and `after_script`:
+It's possible to overwrite the globally defined `before_script` and `after_script`:
```yaml
before_script:
@@ -996,6 +1003,9 @@ job:
### coverage
+**Notes:**
+- [Introduced][ce-7447] in GitLab 8.17.
+
`coverage` allows you to configure how code coverage will be extracted from the
job output.
@@ -1027,7 +1037,7 @@ There are three possible values: `clone`, `fetch`, and `none`.
`clone` is the slowest option. It clones the repository from scratch for every
job, ensuring that the project workspace is always pristine.
-```
+```yaml
variables:
GIT_STRATEGY: clone
```
@@ -1036,7 +1046,7 @@ variables:
if it doesn't exist). `git clean` is used to undo any changes made by the last
job, and `git fetch` is used to retrieve commits made since the last job ran.
-```
+```yaml
variables:
GIT_STRATEGY: fetch
```
@@ -1047,7 +1057,7 @@ for jobs that operate exclusively on artifacts (e.g., `deploy`). Git repository
data may be present, but it is certain to be out of date, so you should only
rely on files brought into the project workspace from cache or artifacts.
-```
+```yaml
variables:
GIT_STRATEGY: none
```
@@ -1061,56 +1071,59 @@ submodules are included when fetching the code before a build. Like
`GIT_STRATEGY`, it can be set in either the global [`variables`](#variables)
section or the [`variables`](#job-variables) section for individual jobs.
-There are three posible values: `none`, `normal`, and `recursive`:
+There are three possible values: `none`, `normal`, and `recursive`:
- `none` means that submodules will not be included when fetching the project
code. This is the default, which matches the pre-v1.10 behavior.
- `normal` means that only the top-level submodules will be included. It is
equivalent to:
+
```
- $ git submodule sync
- $ git submodule update --init
+ git submodule sync
+ git submodule update --init
```
- `recursive` means that all submodules (including submodules of submodules)
will be included. It is equivalent to:
+
```
- $ git submodule sync --recursive
- $ git submodule update --init --recursive
+ git submodule sync --recursive
+ git submodule update --init --recursive
```
Note that for this feature to work correctly, the submodules must be configured
(in `.gitmodules`) with either:
+
- the HTTP(S) URL of a publicly-accessible repository, or
- a relative path to another repository on the same GitLab server. See the
[Git submodules](../git_submodules.md) documentation.
-## Build stages attempts
+## Job stages attempts
> Introduced in GitLab, it requires GitLab Runner v1.9+.
-You can set the number for attempts the running build will try to execute each
+You can set the number for attempts the running job will try to execute each
of the following stages:
-| Variable | Description |
-|-------------------------|-------------|
-| **GET_SOURCES_ATTEMPTS** | Number of attempts to fetch sources running a build |
-| **ARTIFACT_DOWNLOAD_ATTEMPTS** | Number of attempts to download artifacts running a build |
-| **RESTORE_CACHE_ATTEMPTS** | Number of attempts to restore the cache running a build |
+| Variable | Description |
+|-------------------------------- |-------------|
+| **GET_SOURCES_ATTEMPTS** | Number of attempts to fetch sources running a job |
+| **ARTIFACT_DOWNLOAD_ATTEMPTS** | Number of attempts to download artifacts running a job |
+| **RESTORE_CACHE_ATTEMPTS** | Number of attempts to restore the cache running a job |
The default is one single attempt.
Example:
-```
+```yaml
variables:
GET_SOURCES_ATTEMPTS: "3"
```
-You can set them in the global [`variables`](#variables) section or the [`variables`](#job-variables)
-section for individual jobs.
+You can set them in the global [`variables`](#variables) section or the
+[`variables`](#job-variables) section for individual jobs.
## Shallow cloning
@@ -1123,21 +1136,22 @@ repositories with a large number of commits or old, large binaries. The value is
passed to `git fetch` and `git clone`.
>**Note:**
-If you use a depth of 1 and have a queue of builds or retry
-builds, jobs may fail.
+If you use a depth of 1 and have a queue of jobs or retry
+jobs, jobs may fail.
-Since Git fetching and cloning is based on a ref, such as a branch name, runners
-can't clone a specific commit SHA. If there are multiple builds in the queue, or
-you are retrying an old build, the commit to be tested needs to be within the
-git history that is cloned. Setting too small a value for `GIT_DEPTH` can make
+Since Git fetching and cloning is based on a ref, such as a branch name, Runners
+can't clone a specific commit SHA. If there are multiple jobs in the queue, or
+you are retrying an old job, the commit to be tested needs to be within the
+Git history that is cloned. Setting too small a value for `GIT_DEPTH` can make
it impossible to run these old commits. You will see `unresolved reference` in
-build logs. You should then reconsider changing `GIT_DEPTH` to a higher value.
+job logs. You should then reconsider changing `GIT_DEPTH` to a higher value.
-Builds that rely on `git describe` may not work correctly when `GIT_DEPTH` is
-set since only part of the git history is present.
+Jobs that rely on `git describe` may not work correctly when `GIT_DEPTH` is
+set since only part of the Git history is present.
To fetch or clone only the last 3 commits:
-```
+
+```yaml
variables:
GIT_DEPTH: "3"
```
@@ -1174,7 +1188,7 @@ Read more about the various [YAML features](https://learnxinyminutes.com/docs/ya
> Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
-YAML also has a handy feature called 'anchors', which let you easily duplicate
+YAML has a handy feature called 'anchors', which lets you easily duplicate
content across your document. Anchors can be used to duplicate/inherit
properties, and is a perfect example to be used with [hidden keys](#hidden-keys)
to provide templates for your jobs.
@@ -1326,17 +1340,17 @@ pages:
- master
```
-Read more on [GitLab Pages user documentation](../../pages/README.md).
+Read more on [GitLab Pages user documentation](../../user/project/pages/index.md).
## Validate the .gitlab-ci.yml
Each instance of GitLab CI has an embedded debug tool called Lint.
You can find the link under `/ci/lint` of your gitlab instance.
-## Skipping builds
+## Skipping jobs
If your commit message contains `[ci skip]` or `[skip ci]`, using any
-capitalization, the commit will be created but the builds will be skipped.
+capitalization, the commit will be created but the jobs will be skipped.
## Examples
@@ -1350,3 +1364,4 @@ CI with various languages.
[ce-6669]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6669
[variables]: ../variables/README.md
[ce-7983]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7983
+[ce-7447]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7447
diff --git a/doc/customization/branded_page_and_email_header.md b/doc/customization/branded_page_and_email_header.md
new file mode 100644
index 00000000000..9a0f0b382fa
--- /dev/null
+++ b/doc/customization/branded_page_and_email_header.md
@@ -0,0 +1,15 @@
+# Changing the logo on the overall page and email header
+
+Navigate to the **Admin** area and go to the **Appearance** page.
+
+Upload the custom logo (**Header logo**) in the section **Navigation bar**.
+
+![appearance](branded_page_and_email_header/appearance.png)
+
+After saving the page, your GitLab navigation bar will contain the custom logo:
+
+![custom_brand_header](branded_page_and_email_header/custom_brand_header.png)
+
+The GitLab pipeline emails will also have the custom logo:
+
+![custom_email_header](branded_page_and_email_header/custom_email_header.png)
diff --git a/doc/customization/branded_page_and_email_header/appearance.png b/doc/customization/branded_page_and_email_header/appearance.png
new file mode 100644
index 00000000000..abbba6f9ac9
--- /dev/null
+++ b/doc/customization/branded_page_and_email_header/appearance.png
Binary files differ
diff --git a/doc/customization/branded_page_and_email_header/custom_brand_header.png b/doc/customization/branded_page_and_email_header/custom_brand_header.png
new file mode 100644
index 00000000000..7390f8a5e4e
--- /dev/null
+++ b/doc/customization/branded_page_and_email_header/custom_brand_header.png
Binary files differ
diff --git a/doc/customization/branded_page_and_email_header/custom_email_header.png b/doc/customization/branded_page_and_email_header/custom_email_header.png
new file mode 100644
index 00000000000..705698ef4a8
--- /dev/null
+++ b/doc/customization/branded_page_and_email_header/custom_email_header.png
Binary files differ
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index e4a0e0b92bc..819578404b6 100644
--- a/doc/development/code_review.md
+++ b/doc/development/code_review.md
@@ -69,7 +69,7 @@ experience, refactors the existing code). Then:
someone else would be confused by it as well.
- After a round of line notes, it can be helpful to post a summary note such as
"LGTM :thumbsup:", or "Just a couple things to address."
-- Avoid accepting a merge request before the build succeeds. Of course, "Merge
+- Avoid accepting a merge request before the job succeeds. Of course, "Merge
When Pipeline Succeeds" (MWPS) is fine.
- If you set the MR to "Merge When Pipeline Succeeds", you should take over
subsequent revisions for anything that would be spotted after that.
diff --git a/doc/development/frontend.md b/doc/development/frontend.md
index 75fdf3d8e63..ba47998de49 100644
--- a/doc/development/frontend.md
+++ b/doc/development/frontend.md
@@ -50,7 +50,7 @@ Let's look into each of them:
This is the index file of your new feature. This is where the root Vue instance
of the new feature should be.
-Don't forget to follow [these steps.][page-specific-javascript]
+Don't forget to follow [these steps.][page_specific_javascript]
**A folder for Components**
@@ -250,23 +250,17 @@ information.
### Running frontend tests
-`rake teaspoon` runs the frontend-only (JavaScript) tests.
+`rake karma` runs the frontend-only (JavaScript) tests.
It consists of two subtasks:
-- `rake teaspoon:fixtures` (re-)generates fixtures
-- `rake teaspoon:tests` actually executes the tests
+- `rake karma:fixtures` (re-)generates fixtures
+- `rake karma:tests` actually executes the tests
-As long as the fixtures don't change, `rake teaspoon:tests` is sufficient
+As long as the fixtures don't change, `rake karma:tests` is sufficient
(and saves you some time).
-If you need to debug your tests and/or application code while they're
-running, navigate to [localhost:3000/teaspoon](http://localhost:3000/teaspoon)
-in your browser, open DevTools, and run tests for individual files by clicking
-on them. This is also much faster than setting up and running tests from the
-command line.
-
Please note: Not all of the frontend fixtures are generated. Some are still static
-files. These will not be touched by `rake teaspoon:fixtures`.
+files. These will not be touched by `rake karma:fixtures`.
## Design Patterns
@@ -323,54 +317,13 @@ gl.MyThing = MyThing;
For our currently-supported browsers, see our [requirements][requirements].
-[rails]: http://rubyonrails.org/
-[haml]: http://haml.info/
-[hamlit]: https://github.com/k0kubun/hamlit
-[hamlit-limits]: https://github.com/k0kubun/hamlit/blob/master/REFERENCE.md#limitations
-[scss]: http://sass-lang.com/
-[es6]: https://babeljs.io/
-[sprockets]: https://github.com/rails/sprockets
-[jquery]: https://jquery.com/
-[vue]: http://vuejs.org/
-[vue-docs]: http://vuejs.org/guide/index.html
-[web-page-test]: http://www.webpagetest.org/
-[pagespeed-insights]: https://developers.google.com/speed/pagespeed/insights/
-[google-devtools-profiling]: https://developers.google.com/web/tools/chrome-devtools/profile/?hl=en
-[browser-diet]: https://browserdiet.com/
-[d3]: https://d3js.org/
-[chartjs]: http://www.chartjs.org/
-[page-specific-js-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/13bb9ed77f405c5f6ee4fdbc964ecf635c9a223f/app/views/projects/graphs/_head.html.haml#L6-8
-[chrome-accessibility-developer-tools]: https://github.com/GoogleChrome/accessibility-developer-tools
-[audit-rules]: https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules
-[observatory-cli]: https://github.com/mozilla/http-observatory-cli
-[qualys-ssl]: https://www.ssllabs.com/ssltest/analyze.html
-[secure_headers]: https://github.com/twitter/secureheaders
-[mdn-csp]: https://developer.mozilla.org/en-US/docs/Web/Security/CSP
-[github-eng-csp]: http://githubengineering.com/githubs-csp-journey/
-[dropbox-csp-1]: https://blogs.dropbox.com/tech/2015/09/on-csp-reporting-and-filtering/
-[dropbox-csp-2]: https://blogs.dropbox.com/tech/2015/09/unsafe-inline-and-nonce-deployment/
-[dropbox-csp-3]: https://blogs.dropbox.com/tech/2015/09/csp-the-unexpected-eval/
-[dropbox-csp-4]: https://blogs.dropbox.com/tech/2015/09/csp-third-party-integrations-and-privilege-separation/
-[mdn-sri]: https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
-[github-eng-sri]: http://githubengineering.com/subresource-integrity/
-[sprockets-sri]: https://github.com/rails/sprockets-rails#sri-support
-[xss]: https://en.wikipedia.org/wiki/Cross-site_scripting
-[scss-style-guide]: scss_styleguide.md
-[requirements]: ../install/requirements.md#supported-web-browsers
-[issue-boards]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/boards
-[environments-table]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/environments
-[page_specific_javascript]: https://docs.gitlab.com/ce/development/frontend.html#page-specific-javascript
-[component-system]: https://vuejs.org/v2/guide/#Composing-with-Components
-[state-management]: https://vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch
-[vue-resource-repo]: https://github.com/pagekit/vue-resource
-[issue-boards-service]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/boards/services/board_service.js.es6
## Gotchas
### Spec errors due to use of ES6 features in `.js` files
If you see very generic JavaScript errors (e.g. `jQuery is undefined`) being
-thrown in Teaspoon, Spinach, or Rspec tests but can't reproduce them manually,
+thrown in Karma, Spinach, or Rspec tests but can't reproduce them manually,
you may have included `ES6`-style JavaScript in files that don't have the
`.js.es6` file extension. Either use ES5-friendly JavaScript or rename the file
you're working in (`git mv <file.js> <file.js.es6>`).
@@ -438,3 +391,46 @@ Scenario: Developer can approve merge request
Then I should see approved merge request "Bug NS-04"
```
+
+
+[rails]: http://rubyonrails.org/
+[haml]: http://haml.info/
+[hamlit]: https://github.com/k0kubun/hamlit
+[hamlit-limits]: https://github.com/k0kubun/hamlit/blob/master/REFERENCE.md#limitations
+[scss]: http://sass-lang.com/
+[es6]: https://babeljs.io/
+[sprockets]: https://github.com/rails/sprockets
+[jquery]: https://jquery.com/
+[vue]: http://vuejs.org/
+[vue-docs]: http://vuejs.org/guide/index.html
+[web-page-test]: http://www.webpagetest.org/
+[pagespeed-insights]: https://developers.google.com/speed/pagespeed/insights/
+[google-devtools-profiling]: https://developers.google.com/web/tools/chrome-devtools/profile/?hl=en
+[browser-diet]: https://browserdiet.com/
+[d3]: https://d3js.org/
+[chartjs]: http://www.chartjs.org/
+[page-specific-js-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/13bb9ed77f405c5f6ee4fdbc964ecf635c9a223f/app/views/projects/graphs/_head.html.haml#L6-8
+[chrome-accessibility-developer-tools]: https://github.com/GoogleChrome/accessibility-developer-tools
+[audit-rules]: https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules
+[observatory-cli]: https://github.com/mozilla/http-observatory-cli
+[qualys-ssl]: https://www.ssllabs.com/ssltest/analyze.html
+[secure_headers]: https://github.com/twitter/secureheaders
+[mdn-csp]: https://developer.mozilla.org/en-US/docs/Web/Security/CSP
+[github-eng-csp]: http://githubengineering.com/githubs-csp-journey/
+[dropbox-csp-1]: https://blogs.dropbox.com/tech/2015/09/on-csp-reporting-and-filtering/
+[dropbox-csp-2]: https://blogs.dropbox.com/tech/2015/09/unsafe-inline-and-nonce-deployment/
+[dropbox-csp-3]: https://blogs.dropbox.com/tech/2015/09/csp-the-unexpected-eval/
+[dropbox-csp-4]: https://blogs.dropbox.com/tech/2015/09/csp-third-party-integrations-and-privilege-separation/
+[mdn-sri]: https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
+[github-eng-sri]: http://githubengineering.com/subresource-integrity/
+[sprockets-sri]: https://github.com/rails/sprockets-rails#sri-support
+[xss]: https://en.wikipedia.org/wiki/Cross-site_scripting
+[scss-style-guide]: scss_styleguide.md
+[requirements]: ../install/requirements.md#supported-web-browsers
+[issue-boards]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/boards
+[environments-table]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/environments
+[page_specific_javascript]: https://docs.gitlab.com/ce/development/frontend.html#page-specific-javascript
+[component-system]: https://vuejs.org/v2/guide/#Composing-with-Components
+[state-management]: https://vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch
+[vue-resource-repo]: https://github.com/pagekit/vue-resource
+[issue-boards-service]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/boards/services/board_service.js.es6
diff --git a/doc/development/gotchas.md b/doc/development/gotchas.md
index 0f78e8238af..565d4b33457 100644
--- a/doc/development/gotchas.md
+++ b/doc/development/gotchas.md
@@ -3,7 +3,7 @@
The purpose of this guide is to document potential "gotchas" that contributors
might encounter or should avoid during development of GitLab CE and EE.
-## Don't `describe` symbols
+## Do not `describe` symbols
Consider the following model spec:
@@ -32,7 +32,7 @@ spec/models/user_spec.rb|6 error| Failure/Error: u = described_class.new NoMeth
Except for the top-level `describe` block, always provide a String argument to
`describe`.
-## Don't assert against the absolute value of a sequence-generated attribute
+## Do not assert against the absolute value of a sequence-generated attribute
Consider the following factory:
@@ -121,7 +121,7 @@ describe API::Labels do
end
```
-## Don't `rescue Exception`
+## Do not `rescue Exception`
See ["Why is it bad style to `rescue Exception => e` in Ruby?"][Exception].
@@ -130,7 +130,7 @@ Rubocop](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-4-stable/.rubocop.yml#L9
[Exception]: http://stackoverflow.com/q/10048173/223897
-## Don't use inline JavaScript in views
+## Do not use inline JavaScript in views
Using the inline `:javascript` Haml filters comes with a
performance overhead. Using inline JavaScript is not a good way to structure your code and should be avoided.
diff --git a/doc/development/licensing.md b/doc/development/licensing.md
index 5d177eb26ee..1f115059fb8 100644
--- a/doc/development/licensing.md
+++ b/doc/development/licensing.md
@@ -64,6 +64,10 @@ Libraries with the following licenses are unacceptable for use:
- [GNU AGPLv3][AGPLv3]: AGPL-licensed libraries cannot be linked to from non-GPL projects.
- [Open Software License (OSL)][OSL]: is a copyleft license. In addition, the FSF [recommend against its use][OSL-GNU].
+## Requesting Approval for Licenses
+
+Libraries that are not listed in the [Acceptable Licenses][Acceptable-Licenses] or [Unacceptable Licenses][Unacceptable-Licenses] list can be submitted to the legal team for review. Please create an issue in the [Organization Repository][Org-Repo] and cc `@gl-legal`. After a decision has been made, the original requestor is responsible for updating this document.
+
## Notes
Decisions regarding the GNU GPL licenses are based on information provided by [The GNU Project][GNU-GPL-FAQ], as well as [the Open Source Initiative][OSI-GPL], which both state that linking GPL libraries makes the program itself GPL.
@@ -96,3 +100,6 @@ Gems which are included only in the "development" or "test" groups by Bundler ar
[OSI-GPL]: https://opensource.org/faq#linking-proprietary-code
[OSL]: https://opensource.org/licenses/OSL-3.0
[OSL-GNU]: https://www.gnu.org/licenses/license-list.en.html#OSL
+[Org-Repo]: https://gitlab.com/gitlab-com/organization
+[Acceptable-Licenses]: #acceptable-licenses
+[Unacceptable-Licenses]: #unacceptable-licenses
diff --git a/doc/development/limit_ee_conflicts.md b/doc/development/limit_ee_conflicts.md
index 568dedf1669..2d82b09f301 100644
--- a/doc/development/limit_ee_conflicts.md
+++ b/doc/development/limit_ee_conflicts.md
@@ -2,19 +2,26 @@
This guide contains best-practices for avoiding conflicts between CE and EE.
-## Context
+## Daily CE Upstream merge
-Usually, GitLab Community Edition is merged into the Enterprise Edition once a
-week. During these merges, it's very common to get conflicts when some changes
-in CE do not apply cleanly to EE.
+GitLab Community Edition is merged daily into the Enterprise Edition (look for
+the [`CE Upstream` merge requests]). The daily merge is currently done manually
+by four individuals.
-There are a few things that can help you as a developer to:
+**If a developer pings you in a `CE Upstream` merge request for help with
+resolving conflicts, please help them because it means that you didn't do your
+job to reduce the conflicts nor to ease their resolution in the first place!**
-- know when your merge request to CE will conflict when merged to EE
-- avoid such conflicts in the first place
-- ease future conflict resolutions if conflict is inevitable
+To avoid the conflicts beforehand when working on CE, there are a few tools and
+techniques that can help you:
-## Check the `rake ee_compat_check` in your merge requests
+- know what are the usual types of conflicts and how to prevent them
+- the CI `rake ee_compat_check` job tells you if you need to open an EE-version
+ of your CE merge request
+
+[`CE Upstream` merge requests]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests?label_name%5B%5D=CE+upstream
+
+## Check the status of the CI `rake ee_compat_check` job
For each commit (except on `master`), the `rake ee_compat_check` CI job tries to
detect if the current branch's changes will conflict during the CE->EE merge.
diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md
index 827db7e99b8..dcd978c4cd3 100644
--- a/doc/development/rake_tasks.md
+++ b/doc/development/rake_tasks.md
@@ -17,14 +17,14 @@ Note: `db:setup` calls `db:seed` but this does nothing.
In order to run the test you can use the following commands:
- `rake spinach` to run the spinach suite
- `rake spec` to run the rspec suite
-- `rake teaspoon` to run the teaspoon test suite
+- `rake karma` to run the karma test suite
- `rake gitlab:test` to run all the tests
-Note: Both `rake spinach` and `rake spec` takes significant time to pass.
+Note: Both `rake spinach` and `rake spec` takes significant time to pass.
Instead of running full test suite locally you can save a lot of time by running
-a single test or directory related to your changes. After you submit merge request
-CI will run full test suite for you. Green CI status in the merge request means
-full test suite is passed.
+a single test or directory related to your changes. After you submit merge request
+CI will run full test suite for you. Green CI status in the merge request means
+full test suite is passed.
Note: You can't run `rspec .` since this will try to run all the `_spec.rb`
files it can find, also the ones in `/tmp`
diff --git a/doc/development/testing.md b/doc/development/testing.md
index dbea6b9c9aa..9b545d7f0f1 100644
--- a/doc/development/testing.md
+++ b/doc/development/testing.md
@@ -31,9 +31,8 @@ GitLab uses [factory_girl] as a test fixture replacement.
## JavaScript
-GitLab uses [Teaspoon] to run its [Jasmine] JavaScript specs. They can be run on
-the command line via `bundle exec teaspoon`, or via a web browser at
-`http://localhost:3000/teaspoon` when the Rails server is running.
+GitLab uses [Karma] to run its [Jasmine] JavaScript specs. They can be run on
+the command line via `bundle exec karma`.
- JavaScript tests live in `spec/javascripts/`, matching the folder structure of
`app/assets/javascripts/`: `app/assets/javascripts/behaviors/autosize.js.es6` has a corresponding
@@ -51,7 +50,7 @@ the command line via `bundle exec teaspoon`, or via a web browser at
[`Notification`](https://developer.mozilla.org/en-US/docs/Web/API/notification),
which will have to be stubbed.
-[Teaspoon]: https://github.com/modeset/teaspoon
+[Karma]: https://github.com/karma-runner/karma
[Jasmine]: https://github.com/jasmine/jasmine
## RSpec
@@ -96,6 +95,25 @@ so we need to set some guidelines for their use going forward:
[lets-not]: https://robots.thoughtbot.com/lets-not
+### Time-sensitive tests
+
+[Timecop](https://github.com/travisjeffery/timecop) is available in our
+Ruby-based tests for verifying things that are time-sensitive. Any test that
+exercises or verifies something time-sensitive should make use of Timecop to
+prevent transient test failures.
+
+Example:
+
+```ruby
+it 'is overdue' do
+ issue = build(:issue, due_date: Date.tomorrow)
+
+ Timecop.freeze(3.days.from_now) do
+ expect(issue).to be_overdue
+ end
+end
+```
+
### Test speed
GitLab has a massive test suite that, without parallelization, can take more
@@ -116,6 +134,10 @@ Here are some things to keep in mind regarding test performance:
### Features / Integration
+GitLab uses [rspec-rails feature specs] to test features in a browser
+environment. These are [capybara] specs running on the headless [poltergeist]
+driver.
+
- Feature specs live in `spec/features/` and should be named
`ROLE_ACTION_spec.rb`, such as `user_changes_password_spec.rb`.
- Use only one `feature` block per feature spec file.
@@ -123,6 +145,10 @@ Here are some things to keep in mind regarding test performance:
- Avoid scenario titles that add no information, such as "successfully."
- Avoid scenario titles that repeat the feature title.
+[rspec-rails feature specs]: https://github.com/rspec/rspec-rails#feature-specs
+[capybara]: https://github.com/teamcapybara/capybara
+[poltergeist]: https://github.com/teampoltergeist/poltergeist
+
## Spinach (feature) tests
GitLab [moved from Cucumber to Spinach](https://github.com/gitlabhq/gitlabhq/pull/1426)
diff --git a/doc/development/ux_guide/copy.md b/doc/development/ux_guide/copy.md
index 5b65d531e54..ead79ba6a10 100644
--- a/doc/development/ux_guide/copy.md
+++ b/doc/development/ux_guide/copy.md
@@ -1,193 +1,179 @@
-# Copy
-
-The copy for GitLab is clear and direct. We strike a clear balance between professional and friendly. We can empathesize with users (such as celebrating completing all Todos), and remain respectful of the importance of the work. We are that trusted, friendly coworker that is helpful and understanding.
-
-The copy and messaging is a core part of the experience of GitLab and the conversation with our users. Follow the below conventions throughout GitLab.
-
-Portions of this page are inspired by work found in the [Material Design guidelines][material design].
-
->**Note:**
-We are currently inconsistent with this guidance. Images below are created to illustrate the point. As this guidance is refined, we will ensure that our experiences align.
-
-## Contents
-* [Brevity](#brevity)
-* [Capitalization and punctuation](#capitalization-and-punctuation)
-* [Terminology](#terminology)
-
----
-
-## Brevity
-Users will skim content, rather than read text carefully.
-When familiar with a web app, users rely on muscle memory, and may read even less when moving quickly.
-A good experience should quickly orient a user, regardless of their experience, to the purpose of the current screen. This should happen without the user having to consciously read long strings of text.
-In general, text is burdensome and adds cognitive load. This is especially pronounced in a powerful productivity tool such as GitLab.
-We should _not_ rely on words as a crutch to explain the purpose of a screen.
-The current navigation and composition of the elements on the screen should get the user 95% there, with the remaining 5% being specific elements such as text.
-This means that, as a rule, copy should be very short. A long message or label is a red flag hinting at design that needs improvement.
-
->**Example:**
-Use `Add` instead of `Add issue` as a button label.
-Preferrably use context and placement of controls to make it obvious what clicking on them will do.
-
----
-
-## Capitalization and punctuation
-
-### Case
-Use sentence case for titles, headings, labels, menu items, and buttons. Use title case when referring to [features][features] or [products][products]. Note that some features are also objects (e.g. “Merge Requests” and “merge requests”).
-
-| :white_check_mark: Do | :no_entry_sign: Don’t |
-| --- | --- |
-| Add issues to the Issue Board feature in GitLab Hosted | Add Issues To The Issue Board Feature In GitLab Hosted |
-
-### Avoid periods
-Avoid using periods in solitary sentences in these elements:
-
-* Labels
-* Hover text
-* Bulleted lists
-* Dialog body text
-
-Periods should be used for:
-
-* Lists or dialogs 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 |
-| --- | --- |
-| Mention someone to notify them. [Learn more](#) | Mention someone to notify them. [Learn more](#). |
-
-| :white_check_mark: **Do** skip periods after solo sentences of body text | :no_entry_sign: **Don’t** place periods after body text if there is only a single sentence |
-| --- | --- |
-| To see the available commands, enter `/gitlab help` | To see the available commands, enter `/gitlab help`. |
-
-### Use contractions
-Don’t make a sentence harder to understand just to follow this rule. For example, “do not” can give more emphasis than “don’t” when needed.
-
-| :white_check_mark: Do | :no_entry_sign: Don’t |
-| --- | --- |
-| it’s, can’t, wouldn’t, you’re, you’ve, haven’t, don’t | it is, cannot, would not, it’ll, should’ve |
-
-### Use numerals for numbers
-Use “1, 2, 3” instead of “one, two, three” for numbers. One exception is when mixing uses of numbers, such as “Enter two 3s.”
-
-| :white_check_mark: Do | :no_entry_sign: Don’t |
-| --- | --- |
-| 3 new commits | Three new commits |
-
-### Punctuation
-Omit punctuation after phrases and labels to create a cleaner and more readable interface. Use punctuation to add clarity or be grammatically correct.
-
-| 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. |
-| 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. |
-| Apostrophe | **’** | `&rsquo;` | `\u2019` | <kbd>⌥ Option</kbd>+<kbd>⇧ Shift</kbd>+<kbd>]</kbd> | <kbd>Alt</kbd>+<kbd>0 1 4 6</kbd> | Use for contractions (I’m, you’re, ’89) and to show possession.<br><br>To show possession, add an *’s* to all single nouns and names, even if they already end in an *s*: “Your issues’s status was changed.” For singular proper names ending in *s*, use only an apostrophe: “James’ commits.” For plurals of a single letter, add an *’s*: “Dot your i’s and cross your t’s.”<br><br>Omit for decades or acronyms: “the 1990s”, “MRs.” |
-| 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. |
-| 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” |
-| Hyphen | **-** | | | | | Use to represent negative numbers, or to avoid ambiguity in adjective-noun or noun-participle pairs. Example: “anti-inflammatory”; “5-mile walk.”<br><br>Omit in commonly understood terms and adverbs that end in *ly*: “frontend”, “greatly improved performance.”<br><br>Omit in the term “open source.” |
-| Parentheses | **( )** | | | | | Use only to define acronyms or jargon: “Secure web connections are based on a technology called SSL (the secure sockets layer).”<br><br>Avoid other uses and instead rewrite the text, or use dashes or commas to set off the information. If parentheses are required: If the parenthetical is a complete, independent sentence, place the period inside the parentheses; if not, the period goes outside. |
-
-When using the <kbd>Alt</kbd> keystrokes in Windows, use the numeric keypad, not the row of numbers above the alphabet, and be sure Num Lock is turned on.
-
----
-
-## Terminology
-Only use the terms in the tables below.
-
-### Projects and Groups
-
-| Term | Use | :no_entry_sign: Don't |
-| ---- | --- | ----- |
-| Members | When discussing the people who are a part of a project or a group. | Don't use `users`. |
-
-### Issues
-
-#### Adjectives (states)
-
-| Term |
-| ---- |
-| Open |
-| Closed |
-| Deleted |
-
->**Example:**
-Use `5 open issues` and don’t use `5 pending issues`.
-
-#### Verbs (actions)
-
-| Term | Use | :no_entry_sign: Don’t |
-| ---- | --- | --- |
-| Add | Add an issue | Don’t use `create` or `new` |
-| View | View an open or closed issue ||
-| Edit | Edit an open or closed issue | Don’t use `update` |
-| Close | Close an open issue ||
-| Re-open | Re-open a closed issue | There should never be a need to use `open` as a verb |
-| Delete | Delete an open or closed issue ||
-
-#### Add issue
-
-When viewing a list of issues, there is a button that is labeled `Add`. Given the context in the example, it is clearly referring to issues. If the context were not clear enough, the label could be `Add issue`. Clicking the button will bring you to the `Add issue` form. Other add flows should be similar.
-
-![Add issue button](img/copy-form-addissuebutton.png)
-
-The form should be titled `Add issue`. The submit button should be labeled `Submit`. Don’t use `Add`, `Create`, `New`, or `Save changes`. The cancel button should be labeled `Cancel`. Don’t use `Back`.
-
-![Add issue form](img/copy-form-addissueform.png)
-
-#### Edit issue
-
-When in context of an issue, the affordance to edit it is labeled `Edit`. If the context is not clear enough, `Edit issue` could be considered. Other edit flows should be similar.
-
-![Edit issue button](img/copy-form-editissuebutton.png)
-
-The form should be titled `Edit issue`. The submit button should be labeled `Save`. Don’t use `Edit`, `Update`, `Submit`, or `Save changes`. The cancel button should be labeled `Cancel`. Don’t use `Back`.
-
-![Edit issue form](img/copy-form-editissueform.png)
-
-
-### Merge requests
-
-#### Adjectives (states)
-
-| Term |
-| ---- |
-| Open |
-| Merged |
-
-#### Verbs (actions)
-
-| Term | Use | :no_entry_sign: Don’t |
-| ---- | --- | --- |
-| Add | Add a merge request | Do not use `create` or `new` |
-| View | View an open or merged merge request ||
-| Edit | Edit an open or merged merge request| Do not use `update` |
-| Approve | Approve an open merge request ||
-| Remove approval, unapproved | Remove approval of an open merge request | Do not use `unapprove` as that is not an English word|
-| Merge | Merge an open merge request ||
-
-### Comments & Discussions
-
-#### Comment
-A **comment** is a written piece of text that users of GitLab can create. Comments have the meta data of author and timestamp. Comments can be added in a variety of contexts, such as issues, merge requests, and discussions.
-
-#### 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.
-
----
-
-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].
-
-[material design]: https://material.io/guidelines/
-[features]: https://about.gitlab.com/features/ "GitLab features page"
-[products]: https://about.gitlab.com/products/ "GitLab products page"
-[serial comma]: https://en.wikipedia.org/wiki/Serial_comma "“Serial comma” in Wikipedia"
-[android project]: http://source.android.com/
-[creative commons]: http://creativecommons.org/licenses/by/2.5/ \ No newline at end of file
+# Copy
+
+The copy for GitLab is clear and direct. We strike a clear balance between professional and friendly. We can empathesize with users (such as celebrating completing all Todos), and remain respectful of the importance of the work. We are that trusted, friendly coworker that is helpful and understanding.
+
+The copy and messaging is a core part of the experience of GitLab and the conversation with our users. Follow the below conventions throughout GitLab.
+
+Portions of this page are inspired by work found in the [Material Design guidelines][material design].
+
+>**Note:**
+We are currently inconsistent with this guidance. Images below are created to illustrate the point. As this guidance is refined, we will ensure that our experiences align.
+
+## Contents
+* [Brevity](#brevity)
+* [Capitalization and punctuation](#capitalization-and-punctuation)
+* [Terminology](#terminology)
+
+---
+
+## Brevity
+Users will skim content, rather than read text carefully.
+When familiar with a web app, users rely on muscle memory, and may read even less when moving quickly.
+A good experience should quickly orient a user, regardless of their experience, to the purpose of the current screen. This should happen without the user having to consciously read long strings of text.
+In general, text is burdensome and adds cognitive load. This is especially pronounced in a powerful productivity tool such as GitLab.
+We should _not_ rely on words as a crutch to explain the purpose of a screen.
+The current navigation and composition of the elements on the screen should get the user 95% there, with the remaining 5% being specific elements such as text.
+This means that, as a rule, copy should be very short. A long message or label is a red flag hinting at design that needs improvement.
+
+>**Example:**
+Use `Add` instead of `Add issue` as a button label.
+Preferrably use context and placement of controls to make it obvious what clicking on them will do.
+
+---
+
+## Capitalization and punctuation
+
+### Case
+Use sentence case for titles, headings, labels, menu items, and buttons. Use title case when referring to [features][features] or [products][products]. Note that some features are also objects (e.g. “Merge Requests” and “merge requests”).
+
+| :white_check_mark: Do | :no_entry_sign: Don’t |
+| --- | --- |
+| Add issues to the Issue Board feature in GitLab Hosted | Add Issues To The Issue Board Feature In GitLab Hosted |
+
+### Avoid periods
+Avoid using periods in solitary sentences in these elements:
+
+* Labels
+* Hover text
+* Bulleted lists
+* Dialog body text
+
+Periods should be used for:
+
+* Lists or dialogs 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 |
+| --- | --- |
+| Mention someone to notify them. [Learn more](#) | Mention someone to notify them. [Learn more](#). |
+
+| :white_check_mark: **Do** skip periods after solo sentences of body text | :no_entry_sign: **Don’t** place periods after body text if there is only a single sentence |
+| --- | --- |
+| To see the available commands, enter `/gitlab help` | To see the available commands, enter `/gitlab help`. |
+
+### Use contractions
+Don’t make a sentence harder to understand just to follow this rule. For example, “do not” can give more emphasis than “don’t” when needed.
+
+| :white_check_mark: Do | :no_entry_sign: Don’t |
+| --- | --- |
+| it’s, can’t, wouldn’t, you’re, you’ve, haven’t, don’t | it is, cannot, would not, it’ll, should’ve |
+
+### Use numerals for numbers
+Use “1, 2, 3” instead of “one, two, three” for numbers. One exception is when mixing uses of numbers, such as “Enter two 3s.”
+
+| :white_check_mark: Do | :no_entry_sign: Don’t |
+| --- | --- |
+| 3 new commits | Three new commits |
+
+### Punctuation
+Omit punctuation after phrases and labels to create a cleaner and more readable interface. Use punctuation to add clarity or be grammatically correct.
+
+| 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. |
+| 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. |
+| Apostrophe | **’** | `&rsquo;` | `\u2019` | <kbd>⌥ Option</kbd>+<kbd>⇧ Shift</kbd>+<kbd>]</kbd> | <kbd>Alt</kbd>+<kbd>0 1 4 6</kbd> | Use for contractions (I’m, you’re, ’89) and to show possession.<br><br>To show possession, add an *’s* to all singular common nouns and names, even if they already end in an *s*: “Look into this worker process’s log.” For singular proper names ending in *s*, use only an apostrophe: “James’ commits.” For plurals of a single letter, add an *’s*: “Dot your i’s and cross your t’s.”<br><br>Omit for decades or acronyms: “the 1990s”, “MRs.” |
+| 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. |
+| 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” |
+| Hyphen | **-** | | | | | Use to represent negative numbers, or to avoid ambiguity in adjective-noun or noun-participle pairs. Example: “anti-inflammatory”; “5-mile walk.”<br><br>Omit in commonly understood terms and adverbs that end in *ly*: “frontend”, “greatly improved performance.”<br><br>Omit in the term “open source.” |
+| Parentheses | **( )** | | | | | Use only to define acronyms or jargon: “Secure web connections are based on a technology called SSL (the secure sockets layer).”<br><br>Avoid other uses and instead rewrite the text, or use dashes or commas to set off the information. If parentheses are required: If the parenthetical is a complete, independent sentence, place the period inside the parentheses; if not, the period goes outside. |
+
+When using the <kbd>Alt</kbd> keystrokes in Windows, use the numeric keypad, not the row of numbers above the alphabet, and be sure Num Lock is turned on.
+
+---
+
+## Terminology
+Only use the terms below.
+
+When using verbs or adjectives:
+* If the context clearly refers to the object, use them alone. Example: `Edit` or `Closed`
+* If the context isn’t clear enough, use them with the object. Example: `Edit issue` or `Closed issues`
+
+### Projects and Groups
+
+| Term | Use | :no_entry_sign: Don't |
+| ---- | --- | ----- |
+| Members | When discussing the people who are a part of a project or a group. | Don't use `users`. |
+
+### Issues
+
+#### Adjectives (states)
+
+| Term | :no_entry_sign: Don’t |
+| ---- | --- |
+| Open | Don’t use `Pending` or `Created` |
+| Closed | Don’t use `Archived` |
+| Deleted | Don’t use `Removed` or `Trashed` |
+
+#### Verbs (actions)
+
+| Term | Use | :no_entry_sign: Don’t |
+| ---- | --- | --- |
+| New | Although it’s not a verb, `New` is a common standard and used for entering the creation mode of a non-existent issue | Don’t use `Create`, `Open`, or `Add` |
+| Create | Only to indicate when or who created an issue ||
+| Add | Associate an existing issue with an item or a list of items | Don’t use `New` or `Create` |
+| View | Open the detail page of an issue | Don’t use `Open` or `See` |
+| Edit | Enter the editing mode of an issue | Don’t use `Change`, `Modify` or `Update` |
+| Submit | Finalize the *creation* process of an issue | Don’t use `Add`, `Create`, `New`, `Open`, `Save` or `Save changes` |
+| Save | Finalize the *editing* process of an issue | Don’t use `Edit`, `Modify`, `Update`, `Submit`, or `Save changes` |
+| Cancel | Cancel the *creation* or *editing* process of an issue | Don’t use `Back`, `Close`, or `Discard` |
+| Close | Close an open issue | Don’t use `Archive` |
+| Re-open | Re-open a closed issue | Don’t use `Open` |
+| Delete | Permanently remove an issue from the system | Don’t use `Remove` |
+| Remove | Remove the association an issue with an item or a list of items | Don’t use `Delete` |
+
+### Merge requests
+
+#### Adjectives (states)
+
+| Term |
+| ---- |
+| Open |
+| Merged |
+
+#### Verbs (actions)
+
+| Term | Use | :no_entry_sign: Don’t |
+| ---- | --- | --- |
+| Add | Add a merge request | Do not use `create` or `new` |
+| View | View an open or merged merge request ||
+| Edit | Edit an open or merged merge request| Do not use `update` |
+| Approve | Approve an open merge request ||
+| Remove approval, unapproved | Remove approval of an open merge request | Do not use `unapprove` as that is not an English word|
+| Merge | Merge an open merge request ||
+
+### Comments & Discussions
+
+#### Comment
+A **comment** is a written piece of text that users of GitLab can create. Comments have the meta data of author and timestamp. Comments can be added in a variety of contexts, such as issues, merge requests, and discussions.
+
+#### 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.
+
+---
+
+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].
+
+[material design]: https://material.io/guidelines/
+[features]: https://about.gitlab.com/features/ "GitLab features page"
+[products]: https://about.gitlab.com/products/ "GitLab products page"
+[serial comma]: https://en.wikipedia.org/wiki/Serial_comma "“Serial comma” in Wikipedia"
+[android project]: http://source.android.com/
+[creative commons]: http://creativecommons.org/licenses/by/2.5/
diff --git a/doc/development/ux_guide/img/copy-form-addissuebutton.png b/doc/development/ux_guide/img/copy-form-addissuebutton.png
deleted file mode 100644
index 8457f0ab2ab..00000000000
--- a/doc/development/ux_guide/img/copy-form-addissuebutton.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/copy-form-addissueform.png b/doc/development/ux_guide/img/copy-form-addissueform.png
deleted file mode 100644
index 89c6b4acdfb..00000000000
--- a/doc/development/ux_guide/img/copy-form-addissueform.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/copy-form-editissuebutton.png b/doc/development/ux_guide/img/copy-form-editissuebutton.png
deleted file mode 100644
index 04bcc2bf831..00000000000
--- a/doc/development/ux_guide/img/copy-form-editissuebutton.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/copy-form-editissueform.png b/doc/development/ux_guide/img/copy-form-editissueform.png
deleted file mode 100644
index 126ef34ea7e..00000000000
--- a/doc/development/ux_guide/img/copy-form-editissueform.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/harry-robison.png b/doc/development/ux_guide/img/harry-robison.png
new file mode 100644
index 00000000000..702a8b02262
--- /dev/null
+++ b/doc/development/ux_guide/img/harry-robison.png
Binary files differ
diff --git a/doc/development/ux_guide/img/james-mackey.png b/doc/development/ux_guide/img/james-mackey.png
new file mode 100644
index 00000000000..6db257c5b39
--- /dev/null
+++ b/doc/development/ux_guide/img/james-mackey.png
Binary files differ
diff --git a/doc/development/ux_guide/img/steven-lyons.png b/doc/development/ux_guide/img/steven-lyons.png
new file mode 100644
index 00000000000..2efe1d0b168
--- /dev/null
+++ b/doc/development/ux_guide/img/steven-lyons.png
Binary files differ
diff --git a/doc/development/ux_guide/users.md b/doc/development/ux_guide/users.md
index 717a902c424..da410a8de7a 100644
--- a/doc/development/ux_guide/users.md
+++ b/doc/development/ux_guide/users.md
@@ -1,16 +1,164 @@
-# Users
+## UX Personas
+* [Nazim Ramesh](#nazim-ramesh)
+ - Small to medium size organisations using GitLab CE
+* [James Mackey](#james-mackey)
+ - Medium to large size organisations using CE or EE
+ - Small organisations using EE
+* [Karolina Plaskaty](#karolina-plaskaty)
+ - Using GitLab.com for personal/hobby projects
+ - Would like to use GitLab at work
+ - Working for a medium to large size organisation
-> TODO: Create personas. Understand the similarities and differences across the below spectrums.
+<hr>
-## Users by organization
+### Nazim Ramesh
+- Small to medium size organisations using GitLab CE
-- Enterprise
-- Medium company
-- Small company
-- Open source communities
+<img src="img/steven-lyons.png" width="300px">
-## Users by role
+#### Demographics
-- Admin
-- Manager
-- Developer
+- **Age**<br>32 years old
+- **Location**<br>Germany
+- **Education**<br>Bachelor of Science in Computer Science
+- **Occupation**<br>Full-stack web developer
+- **Programming experience**<br>Over 10 years
+- **Frequently used programming languages**<br>JavaScript, SQL, PHP
+- **Hobbies / interests**<br>Functional programming, open source, gaming, web development and web security.
+
+#### Motivations
+Steven works for a software development company which currently hires around 80 people. When Steven first joined the company, the engineering team were using Subversion (SVN) as their primary form of source control. However, Steven felt SVN was not flexible enough to work with many feature branches and noticed that developers with less experience of source control struggled with the central-repository nature of SVN. Armed with a wishlist of features, Steven began comparing source control tools. A search for “self-hosted Git server repository management” returned GitLab. In his own words, Steven explains why he wanted the engineering team to start using GitLab:
+
+>
+“I wanted them to switch away from SVN. I needed a server application to manage repositories. The common tools that were around just didn’t meet the requirements. Most of them were too simple or plain...GitLab provided all the required features. Also costs had to be low, since we don’t have a big budget for those things...the Community Edition was perfect in this regard.”
+>
+
+In his role as a full-stack web developer, Steven could recommend products that he would like the engineering team to use, but final approval lay with his line manager, Mike, VP of Engineering. Steven recalls that he was met with reluctance from his colleagues when he raised moving to Git and using GitLab.
+
+>
+“The biggest challenge...why should we change anything at all from the status quo? We needed to switch from SVN to Git. They knew they needed to learn Git and a Git workflow...using Git was scary to my colleagues...they thought it was more complex than SVN to use.”
+>
+
+Undeterred, Steven decided to migrate a couple of projects across to GitLab.
+
+>
+“Old SVN users couldn’t see the benefits of Git at first. It took a month or two to convince them.”
+>
+
+Slowly, by showing his colleagues how easy it was to use Git, the majority of the team’s projects were migrated to GitLab.
+
+The engineering team have been using GitLab CE for around 2 years now. Steven credits himself as being entirely responsible for his company’s decision to move to GitLab.
+
+#### Frustrations
+##### Adoption to GitLab has been slow
+Not only has the engineering team had to get to grips with Git, they’ve also had to adapt to using GitLab. Due to lack of training and existing skills in other tools, the full feature set of GitLab CE is not being utilised. Steven sold GitLab to his manager as an ‘all in one’ tool which would replace multiple tools used within the company, thus saving costs. Steven hasn’t had the time to integrate the legacy tools to GitLab and he’s struggling to convince his peers to change their habits.
+
+##### Missing Features
+Steven’s company want GitLab to be able to do everything. There isn’t a large budget for software, so they’re selective about what tools are implemented. It needs to add real value to the company. In order for GitLab to be widely adopted and to meet the requirements of different roles within the company, it needs a host of features. When an individual within Steven’s company wants to know if GitLab has a specific feature or does a particular thing, Steven is the person to ask. He becomes the point of contact to investigate, build or sometimes just raise the feature request. Steven gets frustrated when GitLab isn’t able to do what he or his colleagues need it to do.
+
+##### Regressions and bugs
+Steven often has to calm down his colleagues, when a release contains regressions or new bugs. As he puts it “every new version adds something awesome, but breaks something”. He feels that “old issues for "minor" annoyances get quickly buried in the mass of open issues and linger for a very long time. More generally, I have the feeling that GitLab focus on adding new functionalities, but overlook a bunch of annoying minor regressions or introduced bugs.” Due to limited resource and expertise within the team, not only is it difficult to remain up-to-date with the frequent release cycle, it’s also counterproductive to fix workflows every month.
+
+##### Uses too much RAM and CPU
+>
+“Memory usages mean that if we host it from a cloud based host like AWS, we spend almost as much on the instance as what we would pay GitHub”
+>
+
+##### UI/UX
+GitLab’s interface initially attracted Steven when he was comparing version control software. He thought it would help his less technical colleagues to adapt to using Git and perhaps, GitLab could be rolled out to other areas of the business, beyond engineering. However, using GitLab’s interface daily has left him frustrated at the lack of personalisation / control over his user experience. He’s also regularly lost in a maze of navigation. Whilst he acknowledges that GitLab listens to its users and that the interface is improving, he becomes annoyed when the changes are too progressive. “Too frequent UI changes. Most of them tend to turn out great after a few cycles of fixes, but the frequency is still far too high for me to feel comfortable to always stay on the current release.”
+
+#### Goals
+* To convince his colleagues to fully adopt GitLab CE, thus improving workflow and collaboration.
+* To use a feature rich version control platform that covers all stages of the development lifecycle, in order to reduce dependencies on other tools.
+* To use an intuitive and stable product, so he can spend more time on his core job responsibilities and less time bug-fixing, guiding colleagues, etc.
+
+<hr>
+
+### James Mackey
+- Medium to large size organisations using CE or EE
+- Small organisations using EE
+
+<img src="img/james-mackey.png" width="300px">
+
+#### Demographics
+
+- **Age**<br>36 years old
+- **Location**<br>US
+- **Education**<br>Masters degree in Computer Science
+- **Occupation**<br>Full-stack web developer
+- **Programming experience**<br>Over 10 years
+- **Frequently used programming languages**<br>JavaScript, SQL, Node.js, Java, PHP, Python
+- **Hobbies / interests**<br>DevOps, open source, web development, science, automation and electronics.
+
+#### Motivations
+James works for a research company which currently hires around 800 staff. He began using GitLab.com back in 2013 for his own open source, hobby projects and loved “the simplicity of installation, administration and use”. After using GitLab for over a year, he began to wonder about using it at work. James explains:
+
+>
+“We first installed the CE edition...on a staging server for a PoC and asked a beta team to use it, specifically for the Merge Request features. Soon other teams began asking us to be beta users too, because the team that was already using GitLab was really enjoying it.”
+>
+
+James and his colleagues also reviewed competitor products including GitHub Enterprise, but they found it “less innovative and with considerable costs...GitLab had the features we wanted at a much lower cost per head than GitHub”.
+
+The company James works for provides employees with a discretionary budget to spend how they want on software, so James and his team decided to upgrade to EE.
+
+James feels partially responsible for his organisation’s decision to start using GitLab.
+
+>
+“It's still up to the teams themselves [to decide] which tools to use. We just had a great experience moving our daily development to GitLab, so other teams have followed the path or are thinking about switching.”
+>
+
+#### Frustrations
+##### Third Party Integration
+Some of GitLab EE’s features are too basic, in particular, issues boards which do not have the level of reporting that James and his team need. Subsequently, they still need to use GitLab EE in conjunction with other tools, such as JIRA. Whilst James feels it isn’t essential for GitLab to meet all his needs (his company are happy for him to use, and pay for, multiple tools), he sometimes isn’t sure what is/isn’t possible with plugins and what level of custom development he and his team will need to do.
+
+##### UX/UI
+James and his team use CI quite heavily for several projects. Whilst they’ve welcomed improvements to the builds and pipelines interface, they still have some difficulty following build process on the different tabs under Pipelines. Some confusion has arisen from not knowing where to find different pieces of information or how to get to the next stages logs from the current stage’s log output screen. They feel more intuitive linking and flow may alleviate the problem. Generally, they feel GitLab’s navigation needs to reviewed and optimised.
+
+##### Permissions
+>
+“There is no granular control over user or group permissions. The permissions for a project are too tightly coupled to the permissions for Gitlab CI/build pipelines.”
+>
+
+#### Goals
+* To be able to integrate third party tools easily with GitLab EE and to create custom integrations and patches where needed.
+* To use GitLab EE primarily for code hosting, merge requests, continuous integration and issue management. Steven and his team want to be able to understand and use these particular features easily.
+* To able to share one instance of GitLab EE with multiple teams across the business. Advanced user management, the ability to separate permissions on different parts of the source code, etc are important to Steven.
+
+<hr>
+
+### Karolina Plaskaty
+- Using GitLab.com for personal/hobby projects
+- Would like to use GitLab at work
+- Working for a medium to large size organisation
+
+<img src="img/harry-robison.png" width="300px">
+
+#### Demographics
+
+- **Age**<br>26 years old
+- **Location**<br>UK
+- **Education**<br>Self taught
+- **Occupation**<br>Junior web-developer
+- **Programming experience**<br>6 years
+- **Frequently used programming languages**<br>JavaScript and SQL
+- **Hobbies / interests**<br>Web development, mobile development, UX, open source, gaming and travel.
+
+#### Motivations
+Harry has been using GitLab.com for around a year. He roughly spends 8 hours every week programming, of that, 2 hours is spent contributing to open source projects. Harry contributes to open source projects to gain programming experience and to give back to the community. He likes GitLab.com for its free private repositories and range of features which provide him with everything he needs for his personal projects. Harry is also a massive fan of GitLab’s values and the fact that it isn’t a “behemoth of a company”. He explains that “displaying every single thing (doc, culture, assumptions, development...) in the open gives me greater confidence to choose Gitlab personally and to recommend it at work.” He’s also an avid reader of GitLab’s blog.
+
+Harry works for a software development company which currently hires around 500 people. Harry would love to use GitLab at work but the company has used GitHub Enterprise for a number of years. He describes management at his company as “old fashioned” and explains that it’s “less of a technical issue and more of a cultural issue” to convince upper management to move to GitLab. Harry is also relatively new to the company so he’s apprehensive about pushing too hard to change version control platforms.
+
+#### Frustrations
+##### Unable to use GitLab at work
+Harry wants to use GitLab at work but isn’t sure how to approach the subject with management. In his current role, he doesn’t feel that he has the authority to request GitLab.
+
+##### Performance
+GitLab.com is frequently slow and unavailable. Harry has also heard that GitLab is a “memory hog” which has deterred him from running GitLab on his own machine for just hobby / personal projects.
+
+##### UX/UI
+Harry has an interest in UX and therefore has strong opinions about how GitLab should look and feel. He feels the interface is cluttered, “it has too many links/buttons” and the navigation “feels a bit weird sometimes. I get lost if I don’t pay attention.” As Harry also enjoys contributing to open-source projects, it’s important to him that GitLab is well designed for public repositories, he doesn’t feel that GitLab currently achieves this.
+
+#### Goals
+* To develop his programming experience and to learn from other developers.
+* To contribute to both his own and other open source projects.
+* To use a fast and intuitive version control platform. \ No newline at end of file
diff --git a/doc/gitlab-basics/command-line-commands.md b/doc/gitlab-basics/command-line-commands.md
index 3b075ff5fc0..2a531193adf 100644
--- a/doc/gitlab-basics/command-line-commands.md
+++ b/doc/gitlab-basics/command-line-commands.md
@@ -25,6 +25,8 @@ git clone PASTE HTTPS OR SSH HERE
A clone of the project will be created in your computer.
+>**Note:** If you clone your project via an URL that contains special characters, make sure that they are URL-encoded.
+
### Go into a project, directory or file to work in it
```
diff --git a/doc/gitlab-basics/img/profile_settings.png b/doc/gitlab-basics/img/profile_settings.png
index 26df4c0a734..aaa1a39313d 100644
--- a/doc/gitlab-basics/img/profile_settings.png
+++ b/doc/gitlab-basics/img/profile_settings.png
Binary files differ
diff --git a/doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.png b/doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.png
index 6a1430d9663..7ebb8973ef0 100644
--- a/doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.png
+++ b/doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.png
Binary files differ
diff --git a/doc/install/database_mysql.md b/doc/install/database_mysql.md
index 65bfb0f7d6e..da2dac23c6a 100644
--- a/doc/install/database_mysql.md
+++ b/doc/install/database_mysql.md
@@ -1,64 +1,67 @@
# Database MySQL
-## Note
-
-We do not recommend using MySQL due to various issues. For example, case [(in)sensitivity](https://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html) and [problems](https://bugs.mysql.com/bug.php?id=65830) that [suggested](https://bugs.mysql.com/bug.php?id=50909) [fixes](https://bugs.mysql.com/bug.php?id=65830) [have](https://bugs.mysql.com/bug.php?id=63164).
+>**Note:**
+We do not recommend using MySQL due to various issues. For example, case
+[(in)sensitivity](https://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html)
+and [problems](https://bugs.mysql.com/bug.php?id=65830) that
+[suggested](https://bugs.mysql.com/bug.php?id=50909)
+[fixes](https://bugs.mysql.com/bug.php?id=65830) [have](https://bugs.mysql.com/bug.php?id=63164).
## Initial database setup
- # Install the database packages
- sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev
-
- # Ensure you have MySQL version 5.5.14 or later
- mysql --version
+```
+# Install the database packages
+sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev
- # Pick a MySQL root password (can be anything), type it and press enter
- # Retype the MySQL root password and press enter
+# Ensure you have MySQL version 5.5.14 or later
+mysql --version
- # Secure your installation
- sudo mysql_secure_installation
+# Pick a MySQL root password (can be anything), type it and press enter
+# Retype the MySQL root password and press enter
- # Login to MySQL
- mysql -u root -p
+# Secure your installation
+sudo mysql_secure_installation
- # Type the MySQL root password
+# Login to MySQL
+mysql -u root -p
- # Create a user for GitLab
- # do not type the 'mysql>', this is part of the prompt
- # change $password in the command below to a real password you pick
- mysql> CREATE USER 'git'@'localhost' IDENTIFIED BY '$password';
+# Type the MySQL root password
- # Ensure you can use the InnoDB engine which is necessary to support long indexes
- # If this fails, check your MySQL config files (e.g. `/etc/mysql/*.cnf`, `/etc/mysql/conf.d/*`) for the setting "innodb = off"
- mysql> SET storage_engine=INNODB;
+# Create a user for GitLab
+# do not type the 'mysql>', this is part of the prompt
+# change $password in the command below to a real password you pick
+mysql> CREATE USER 'git'@'localhost' IDENTIFIED BY '$password';
- # If you have MySQL < 5.7.7 and want to enable utf8mb4 character set support with your GitLab install, you must set the following NOW:
- mysql> SET GLOBAL innodb_file_per_table=1, innodb_file_format=Barracuda, innodb_large_prefix=1;
+# Ensure you can use the InnoDB engine which is necessary to support long indexes
+# If this fails, check your MySQL config files (e.g. `/etc/mysql/*.cnf`, `/etc/mysql/conf.d/*`) for the setting "innodb = off"
+mysql> SET storage_engine=INNODB;
- # Create the GitLab production database
- mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_general_ci`;
+# If you have MySQL < 5.7.7 and want to enable utf8mb4 character set support with your GitLab install, you must set the following NOW:
+mysql> SET GLOBAL innodb_file_per_table=1, innodb_file_format=Barracuda, innodb_large_prefix=1;
- # Grant the GitLab user necessary permissions on the database
- mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER, LOCK TABLES, REFERENCES ON `gitlabhq_production`.* TO 'git'@'localhost';
+# Create the GitLab production database
+mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_general_ci`;
- # Quit the database session
- mysql> \q
+# Grant the GitLab user necessary permissions on the database
+mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER, LOCK TABLES, REFERENCES ON `gitlabhq_production`.* TO 'git'@'localhost';
- # Try connecting to the new database with the new user
- sudo -u git -H mysql -u git -p -D gitlabhq_production
+# Quit the database session
+mysql> \q
- # Type the password you replaced $password with earlier
+# Try connecting to the new database with the new user
+sudo -u git -H mysql -u git -p -D gitlabhq_production
- # You should now see a 'mysql>' prompt
+# Type the password you replaced $password with earlier
- # Quit the database session
- mysql> \q
+# You should now see a 'mysql>' prompt
- # You are done installing the database for now and can go back to the rest of the installation.
+# Quit the database session
+mysql> \q
+```
+You are done installing the database for now and can go back to the rest of the installation.
Please proceed to the rest of the installation before running through the utf8mb4 support section.
-
### MySQL utf8mb4 support
After installation or upgrade, remember to [convert any new tables](#convert) to `utf8mb4`/`utf8mb4_general_ci`.
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 355179960b3..5ba338ba7d1 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -39,6 +39,7 @@ The GitLab installation consists of setting up the following components:
1. Packages / Dependencies
1. Ruby
1. Go
+1. Node
1. System Users
1. Database
1. Redis
@@ -63,7 +64,7 @@ up-to-date and install it.
Install the required packages (needed to compile Ruby and native extensions to Ruby gems):
- sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate python-docutils pkg-config cmake nodejs
+ sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate python-docutils pkg-config cmake
If you want to use Kerberos for user authentication, then install libkrb5-dev:
@@ -151,13 +152,30 @@ page](https://golang.org/dl).
sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/
rm go1.5.3.linux-amd64.tar.gz
-## 4. System Users
+## 4. Node
+
+Since GitLab 8.17, GitLab requires the use of node >= v4.3.0 to compile
+javascript assets, and starting in GitLab 9.0, yarn >= v0.17.0 is required to
+manage javascript dependencies. In many distros the versions provided by the
+official package repositories are out of date, so we'll need to install through
+the following commands:
+
+ # install node v7.x
+ curl --location https://deb.nodesource.com/setup_7.x | bash -
+ sudo apt-get install -y nodejs
+
+ # install yarn
+ curl --location https://yarnpkg.com/install.sh | bash -
+
+Visit the official websites for [node](https://nodejs.org/en/download/package-manager/) and [yarn](https://yarnpkg.com/en/docs/install/) if you have any trouble with these steps.
+
+## 5. System Users
Create a `git` user for GitLab:
sudo adduser --disabled-login --gecos 'GitLab' git
-## 5. Database
+## 6. Database
We recommend using a PostgreSQL database. For MySQL check the
[MySQL setup guide](database_mysql.md).
@@ -218,7 +236,7 @@ We recommend using a PostgreSQL database. For MySQL check the
gitlabhq_production> \q
```
-## 6. Redis
+## 7. Redis
GitLab requires at least Redis 2.8.
@@ -263,7 +281,7 @@ sudo service redis-server restart
sudo usermod -aG redis git
```
-## 7. GitLab
+## 8. GitLab
# We'll install GitLab into home directory of the user "git"
cd /home/git
@@ -307,7 +325,7 @@ sudo usermod -aG redis git
# now that files in public/uploads are served by gitlab-workhorse
sudo chmod 0700 public/uploads
- # Change the permissions of the directory where CI build traces are stored
+ # Change the permissions of the directory where CI job traces are stored
sudo chmod -R u+rwX builds/
# Change the permissions of the directory where CI artifacts are stored
@@ -451,7 +469,8 @@ Check if GitLab and its environment are configured correctly:
### Compile Assets
- sudo -u git -H bundle exec rake gitlab:assets:compile RAILS_ENV=production
+ sudo -u git -H yarn install --production --pure-lockfile
+ sudo -u git -H bundle exec rake gitlab:assets:compile RAILS_ENV=production NODE_ENV=production
### Start Your GitLab Instance
@@ -459,7 +478,7 @@ Check if GitLab and its environment are configured correctly:
# or
sudo /etc/init.d/gitlab restart
-## 8. Nginx
+## 9. Nginx
**Note:** Nginx is the officially supported web server for GitLab. If you cannot or do not want to use Nginx as your web server, have a look at the [GitLab recipes](https://gitlab.com/gitlab-org/gitlab-recipes/).
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index e942346e2d7..3f90597ec80 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -15,11 +15,11 @@ For the installations options please see [the installation page on the GitLab we
### Unsupported Unix distributions
-- OS X
- Arch Linux
- Fedora
-- Gentoo
- FreeBSD
+- Gentoo
+- macOS
On the above unsupported distributions is still possible to install GitLab yourself.
Please see the [installation from source guide](installation.md) and the [installation guides](https://about.gitlab.com/installation/) for more information.
@@ -120,7 +120,12 @@ To change the Unicorn workers when you have the Omnibus package please see [the
## Database
-If you want to run the database separately expect a size of about 1 MB per user.
+We currently support the following databases:
+
+- PostgreSQL (recommended)
+- MySQL/MariaDB
+
+If you want to run the database separately, expect a size of about 1 MB per user.
### PostgreSQL Requirements
@@ -128,7 +133,9 @@ Users using PostgreSQL must ensure the `pg_trgm` extension is loaded into every
GitLab database. This extension can be enabled (using a PostgreSQL super user)
by running the following query for every database:
- CREATE EXTENSION pg_trgm;
+```
+CREATE EXTENSION pg_trgm;
+```
On some systems you may need to install an additional package (e.g.
`postgresql-contrib`) for this extension to become available.
diff --git a/doc/integration/auth0.md b/doc/integration/auth0.md
index b10f7054482..c39d7ab57c6 100644
--- a/doc/integration/auth0.md
+++ b/doc/integration/auth0.md
@@ -80,10 +80,13 @@ from step 5.
1. Change `YOUR_AUTH0_CLIENT_SECRET` to the client secret from the Auth0 Console
page from step 5.
-1. Save the file and [reconfigure GitLab](../administration/restart_gitlab.md)
-for the changes to take effect.
+1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
+ installed GitLab via Omnibus or from source respectively.
On the sign in page there should now be an Auth0 icon below the regular sign in
form. Click the icon to begin the authentication process. Auth0 will ask the
user to sign in and authorize the GitLab application. If everything goes well
the user will be returned to GitLab and will be signed in.
+
+[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
diff --git a/doc/integration/azure.md b/doc/integration/azure.md
index 48dddf7df44..5e3e9f5ab77 100644
--- a/doc/integration/azure.md
+++ b/doc/integration/azure.md
@@ -78,6 +78,10 @@ To enable the Microsoft Azure OAuth2 OmniAuth provider you must register your ap
1. Save the configuration file.
-1. Restart GitLab for the changes to take effect.
+1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
+ installed GitLab via Omnibus or from source respectively.
On the sign in page there should now be a Microsoft icon below the regular sign in form. Click the icon to begin the authentication process. Microsoft will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to GitLab and will be signed in.
+
+[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
diff --git a/doc/integration/cas.md b/doc/integration/cas.md
index e34e306f9ac..f757edf0bc2 100644
--- a/doc/integration/cas.md
+++ b/doc/integration/cas.md
@@ -58,8 +58,11 @@ To enable the CAS OmniAuth provider you must register your application with your
1. Save the configuration file.
-1. Run `gitlab-ctl reconfigure` for the omnibus package.
-
-1. Restart GitLab for the changes to take effect.
+1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
+ installed GitLab via Omnibus or from source respectively.
On the sign in page there should now be a CAS tab in the sign in form.
+
+[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
+
diff --git a/doc/integration/crowd.md b/doc/integration/crowd.md
index 40d93aef2a9..f8370cd349e 100644
--- a/doc/integration/crowd.md
+++ b/doc/integration/crowd.md
@@ -53,6 +53,11 @@ To enable the Crowd OmniAuth provider you must register your application with Cr
1. Save the configuration file.
-1. Restart GitLab for the changes to take effect.
+1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
+ installed GitLab via Omnibus or from source respectively.
+
+On the sign in page there should now be a Crowd tab in the sign in form.
+
+[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
-On the sign in page there should now be a Crowd tab in the sign in form. \ No newline at end of file
diff --git a/doc/integration/facebook.md b/doc/integration/facebook.md
index 77bb75cbfca..a67de23b17b 100644
--- a/doc/integration/facebook.md
+++ b/doc/integration/facebook.md
@@ -92,6 +92,10 @@ something else descriptive.
1. Save the configuration file.
-1. Restart GitLab for the changes to take effect.
+1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
+ installed GitLab via Omnibus or from source respectively.
On the sign in page there should now be a Facebook icon below the regular sign in form. Click the icon to begin the authentication process. Facebook will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to GitLab and will be signed in.
+
+[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
diff --git a/doc/integration/github.md b/doc/integration/github.md
index 479c697b933..cea85f073cc 100644
--- a/doc/integration/github.md
+++ b/doc/integration/github.md
@@ -2,7 +2,7 @@
Import projects from GitHub and login to your GitLab instance with your GitHub account.
-To enable the GitHub OmniAuth provider you must register your application with GitHub.
+To enable the GitHub OmniAuth provider you must register your application with GitHub.
GitHub will generate an application ID and secret key for you to use.
1. Sign in to GitHub.
@@ -22,7 +22,7 @@ GitHub will generate an application ID and secret key for you to use.
- Authorization callback URL is 'http(s)://${YOUR_DOMAIN}'
1. Select "Register application".
-1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot).
+1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot).
Keep this page open as you continue configuration.
![GitHub app](img/github_app.png)
@@ -49,7 +49,7 @@ GitHub will generate an application ID and secret key for you to use.
For omnibus package:
For GitHub.com:
-
+
```ruby
gitlab_rails['omniauth_providers'] = [
{
@@ -60,9 +60,9 @@ GitHub will generate an application ID and secret key for you to use.
}
]
```
-
+
For GitHub Enterprise:
-
+
```ruby
gitlab_rails['omniauth_providers'] = [
{
@@ -101,10 +101,14 @@ GitHub will generate an application ID and secret key for you to use.
1. Change 'YOUR_APP_SECRET' to the client secret from the GitHub application page from step 7.
-1. Save the configuration file and run `sudo gitlab-ctl reconfigure`.
+1. Save the configuration file.
-1. Restart GitLab for the changes to take effect.
+1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
+ installed GitLab via Omnibus or from source respectively.
-On the sign in page there should now be a GitHub icon below the regular sign in form.
-Click the icon to begin the authentication process. GitHub will ask the user to sign in and authorize the GitLab application.
+On the sign in page there should now be a GitHub icon below the regular sign in form.
+Click the icon to begin the authentication process. GitHub will ask the user to sign in and authorize the GitLab application.
If everything goes well the user will be returned to GitLab and will be signed in.
+
+[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
diff --git a/doc/integration/gitlab.md b/doc/integration/gitlab.md
index 6d8f3912ede..eec40a9b8f1 100644
--- a/doc/integration/gitlab.md
+++ b/doc/integration/gitlab.md
@@ -2,7 +2,7 @@
Import projects from GitLab.com and login to your GitLab instance with your GitLab.com account.
-To enable the GitLab.com OmniAuth provider you must register your application with GitLab.com.
+To enable the GitLab.com OmniAuth provider you must register your application with GitLab.com.
GitLab.com will generate an application ID and secret key for you to use.
1. Sign in to GitLab.com
@@ -26,8 +26,8 @@ GitLab.com will generate an application ID and secret key for you to use.
1. Select "Submit".
-1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot).
- Keep this page open as you continue configuration.
+1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot).
+ Keep this page open as you continue configuration.
![GitLab app](img/gitlab_app.png)
1. On your GitLab server, open the configuration file.
@@ -77,8 +77,12 @@ GitLab.com will generate an application ID and secret key for you to use.
1. Save the configuration file.
-1. Restart GitLab for the changes to take effect.
+1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
+ installed GitLab via Omnibus or from source respectively.
-On the sign in page there should now be a GitLab.com icon below the regular sign in form.
-Click the icon to begin the authentication process. GitLab.com will ask the user to sign in and authorize the GitLab application.
+On the sign in page there should now be a GitLab.com icon below the regular sign in form.
+Click the icon to begin the authentication process. GitLab.com will ask the user to sign in and authorize the GitLab application.
If everything goes well the user will be returned to your GitLab instance and will be signed in.
+
+[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
diff --git a/doc/integration/google.md b/doc/integration/google.md
index 82978b68a34..1e7ad90c5a8 100644
--- a/doc/integration/google.md
+++ b/doc/integration/google.md
@@ -74,7 +74,8 @@ To enable the Google OAuth2 OmniAuth provider you must register your application
1. Save the configuration file.
-1. Restart GitLab for the changes to take effect.
+1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
+ installed GitLab via Omnibus or from source respectively.
On the sign in page there should now be a Google icon below the regular sign in form. Click the icon to begin the authentication process. Google will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to GitLab and will be signed in.
@@ -87,3 +88,6 @@ At this point, when users first try to authenticate to your GitLab installation
1. Select 'Consent screen' in the left menu. (See steps 1, 4 and 5 above for instructions on how to get here if you closed your window).
1. Scroll down until you find "Product Name". Change the product name to something more descriptive.
1. Add any additional information as you wish - homepage, logo, privacy policy, etc. None of this is required, but it may help your users.
+
+[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
diff --git a/doc/integration/ldap.md b/doc/integration/ldap.md
index 30f0c15dacc..242890af981 100644
--- a/doc/integration/ldap.md
+++ b/doc/integration/ldap.md
@@ -1,3 +1 @@
-# GitLab LDAP integration
-
-This document was moved under [`administration/auth/ldap`](../administration/auth/ldap.md).
+This document was moved to [`administration/auth/ldap`](../administration/auth/ldap.md).
diff --git a/doc/integration/oauth2_generic.md b/doc/integration/oauth2_generic.md
new file mode 100644
index 00000000000..e71706fef7d
--- /dev/null
+++ b/doc/integration/oauth2_generic.md
@@ -0,0 +1,65 @@
+# Sign into GitLab with (almost) any OAuth2 provider
+
+The `omniauth-oauth2-generic` gem allows Single Sign On between GitLab and your own OAuth2 provider
+(or any OAuth2 provider compatible with this gem)
+
+This strategy is designed to allow configuration of the simple OmniAuth SSO process outlined below:
+
+1. Strategy directs client to your authorization URL (**configurable**), with specified ID and key
+1. OAuth provider handles authentication of request, user, and (optionally) authorization to access user's profile
+1. OAuth provider directs client back to GitLab where Strategy handles retrieval of access token
+1. Strategy requests user information from a **configurable** "user profile" URL (using the access token)
+1. Strategy parses user information from the response, using a **configurable** format
+1. GitLab finds or creates the returned user and logs them in
+
+### Limitations of this Strategy:
+
+- It can only be used for Single Sign on, and will not provide any other access granted by any OAuth provider
+ (importing projects or users, etc)
+- It only supports the Authorization Grant flow (most common for client-server applications, like GitLab)
+- It is not able to fetch user information from more than one URL
+- It has not been tested with user information formats other than JSON
+
+### Config Instructions
+
+1. Register your application in the OAuth2 provider you wish to authenticate with.
+
+ The redirect URI you provide when registering the application should be:
+
+ ```
+ http://your-gitlab.host.com/users/auth/oauth2_generic/callback
+ ```
+
+1. You should now be able to get a Client ID and Client Secret.
+ Where this shows up will differ for each provider.
+ This may also be called Application ID and Secret
+
+1. On your GitLab server, open the configuration file.
+
+ For Omnibus package:
+
+ ```sh
+ sudo editor /etc/gitlab/gitlab.rb
+ ```
+
+ For installations from source:
+
+ ```sh
+ cd /home/git/gitlab
+ sudo -u git -H editor config/gitlab.yml
+ ```
+
+1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings
+
+1. Add the provider-specific configuration for your provider, as [described in the gem's README][1]
+
+1. Save the configuration file
+
+1. Restart GitLab for the changes to take effect
+
+On the sign in page there should now be a new button below the regular sign in form.
+Click the button to begin your provider's authentication process. This will direct
+the browser to your OAuth2 Provider's authentication page. If everything goes well
+the user will be returned to your GitLab instance and will be signed in.
+
+[1]: https://gitlab.com/satorix/omniauth-oauth2-generic#gitlab-config-example \ No newline at end of file
diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md
index 98a680d0dbe..47e20d7566a 100644
--- a/doc/integration/omniauth.md
+++ b/doc/integration/omniauth.md
@@ -31,6 +31,7 @@ contains some settings that are common for all providers.
- [Azure](azure.md)
- [Auth0](auth0.md)
- [Authentiq](../administration/auth/authentiq.md)
+- [OAuth2Generic](oauth2_generic.md)
## Initial OmniAuth Configuration
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
index 4a242c321aa..2277aa827b7 100644
--- a/doc/integration/saml.md
+++ b/doc/integration/saml.md
@@ -74,7 +74,7 @@ in your SAML IdP:
idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
idp_sso_target_url: 'https://login.example.com/idp',
issuer: 'https://gitlab.example.com',
- name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
+ name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'
},
label: 'Company Login' # optional label for SAML login button, defaults to "Saml"
}
@@ -91,7 +91,7 @@ in your SAML IdP:
idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
idp_sso_target_url: 'https://login.example.com/idp',
issuer: 'https://gitlab.example.com',
- name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
+ name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'
},
label: 'Company Login' # optional label for SAML login button, defaults to "Saml"
}
@@ -109,7 +109,8 @@ in your SAML IdP:
1. Change the value of `issuer` to a unique name, which will identify the application
to the IdP.
-1. Restart GitLab for the changes to take effect.
+1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
+ installed GitLab via Omnibus or from source respectively.
1. Register the GitLab SP in your SAML 2.0 IdP, using the application name specified
in `issuer`.
@@ -171,7 +172,7 @@ tell GitLab which groups are external via the `external_groups:` element:
idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
idp_sso_target_url: 'https://login.example.com/idp',
issuer: 'https://gitlab.example.com',
- name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
+ name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'
} }
```
@@ -226,7 +227,7 @@ args: {
idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
idp_sso_target_url: 'https://login.example.com/idp',
issuer: 'https://gitlab.example.com',
- name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient',
+ name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
attribute_statements: { email: ['EmailAddress'] }
}
```
@@ -244,7 +245,7 @@ args: {
idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
idp_sso_target_url: 'https://login.example.com/idp',
issuer: 'https://gitlab.example.com',
- name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient',
+ name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
attribute_statements: { email: ['EmailAddress'] },
allowed_clock_drift: 1 # for one second clock drift
}
@@ -314,3 +315,6 @@ For this you need take the following into account:
Make sure that one of the above described scenarios is valid, or the requests will
fail with one of the mentioned errors.
+
+[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
diff --git a/doc/integration/shibboleth.md b/doc/integration/shibboleth.md
index 696c1011eeb..e0fc1bb801f 100644
--- a/doc/integration/shibboleth.md
+++ b/doc/integration/shibboleth.md
@@ -70,10 +70,9 @@ gitlab_rails['omniauth_providers'] = [
]
```
-1. Save changes and reconfigure gitlab:
-```
-sudo gitlab-ctl reconfigure
-```
+
+1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
+ installed GitLab via Omnibus or from source respectively.
On the sign in page there should now be a "Sign in with: Shibboleth" icon below the regular sign in form. Click the icon to begin the authentication process. You will be redirected to IdP server (Depends on your Shibboleth module configuration). If everything goes well the user will be returned to GitLab and will be signed in.
@@ -122,4 +121,7 @@ you will not get a shibboleth session!
RequestHeader set X_FORWARDED_PROTO 'https'
RequestHeader set X-Forwarded-Ssl on
-``` \ No newline at end of file
+```
+
+[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
diff --git a/doc/integration/twitter.md b/doc/integration/twitter.md
index abbea09f22f..d0976b6201e 100644
--- a/doc/integration/twitter.md
+++ b/doc/integration/twitter.md
@@ -74,6 +74,10 @@ To enable the Twitter OmniAuth provider you must register your application with
1. Save the configuration file.
-1. Restart GitLab for the changes to take effect.
+1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
+ installed GitLab via Omnibus or from source respectively.
On the sign in page there should now be a Twitter icon below the regular sign in form. Click the icon to begin the authentication process. Twitter will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to GitLab and will be signed in.
+
+[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
diff --git a/doc/pages/README.md b/doc/pages/README.md
deleted file mode 100644
index c9715eed598..00000000000
--- a/doc/pages/README.md
+++ /dev/null
@@ -1 +0,0 @@
-This document was moved to [user/project/pages](../user/project/pages/index.md).
diff --git a/doc/pages/getting_started_part_one.md b/doc/pages/getting_started_part_one.md
new file mode 100644
index 00000000000..c5b1aa4b654
--- /dev/null
+++ b/doc/pages/getting_started_part_one.md
@@ -0,0 +1,266 @@
+# GitLab Pages from A to Z: Part 1
+
+- **Part 1: Static Sites, Domains, DNS Records, and SSL/TLS Certificates**
+- _[Part 2: Quick Start Guide - Setting Up GitLab Pages](getting_started_part_two.md)_
+- _[Part 3: Creating and Tweaking `.gitlab-ci.yml` for GitLab Pages](getting_started_part_three.md)_
+
+----
+
+This is a comprehensive guide, made for those who want to
+publish a website with GitLab Pages but aren't familiar with
+the entire process involved.
+
+To **enable** GitLab Pages for GitLab CE (Community Edition)
+and GitLab EE (Enterprise Edition), please read the
+[admin documentation](https://docs.gitlab.com/ce/administration/pages/index.html),
+and/or watch this [video tutorial](https://youtu.be/dD8c7WNcc6s).
+
+>**Note:**
+For this guide, we assume you already have GitLab Pages
+server up and running for your GitLab instance.
+
+## What you need to know before getting started
+
+Before we begin, let's understand a few concepts first.
+
+### Static sites
+
+GitLab Pages only supports static websites, meaning,
+your output files must be HTML, CSS, and JavaScript only.
+
+To create your static site, you can either hardcode in HTML,
+CSS, and JS, or use a [Static Site Generator (SSG)](https://www.staticgen.com/)
+to simplify your code and build the static site for you,
+which is highly recommendable and much faster than hardcoding.
+
+---
+
+- Read through this technical overview on [Static versus Dynamic Websites](https://about.gitlab.com/2016/06/03/ssg-overview-gitlab-pages-part-1-dynamic-x-static/)
+- Understand [how modern Static Site Generators work](https://about.gitlab.com/2016/06/10/ssg-overview-gitlab-pages-part-2/) and what you can add to your static site
+- You can use [any SSG with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/)
+- Fork an [example project](https://gitlab.com/pages) to build your website based upon
+
+### GitLab Pages domain
+
+If you set up a GitLab Pages project on GitLab.com,
+it will automatically be accessible under a
+[subdomain of `namespace.pages.io`](https://docs.gitlab.com/ce/user/project/pages/).
+The `namespace` is defined by your username on GitLab.com,
+or the group name you created this project under.
+
+>**Note:**
+If you use your own GitLab instance to deploy your
+site with GitLab Pages, check with your sysadmin what's your
+Pages wildcard domain. This guide is valid for any GitLab instance,
+you just need to replace Pages wildcard domain on GitLab.com
+(`*.gitlab.io`) with your own.
+
+#### Practical examples
+
+**Project Websites:**
+
+- You created a project called `blog` under your username `john`,
+therefore your project URL is `https://gitlab.com/john/blog/`.
+Once you enable GitLab Pages for this project, and build your site,
+it will be available under `https://john.gitlab.io/blog/`.
+- You created a group for all your websites called `websites`,
+and a project within this group is called `blog`. Your project
+URL is `https://gitlab.com/websites/blog/`. Once you enable
+GitLab Pages for this project, the site will live under
+`https://websites.gitlab.io/blog/`.
+
+**User and Group Websites:**
+
+- Under your username, `john`, you created a project called
+`john.gitlab.io`. Your project URL will be `https://gitlab.com/john/john.gitlab.io`.
+Once you enable GitLab Pages for your project, your website
+will be published under `https://john.gitlab.io`.
+- Under your group `websites`, you created a project called
+`websites.gitlab.io`. your project's URL will be `https://gitlab.com/websites/websites.gitlab.io`. Once you enable GitLab Pages for your project,
+your website will be published under `https://websites.gitlab.io`.
+
+**General example:**
+
+- On GitLab.com, a project site will always be available under
+`https://namespace.gitlab.io/project-name`
+- On GitLab.com, a user or group website will be available under
+`https://namespace.gitlab.io/`
+- On your GitLab instance, replace `gitlab.io` above with your
+Pages server domain. Ask your sysadmin for this information.
+
+### DNS Records
+
+A Domain Name System (DNS) web service routes visitors to websites
+by translating domain names (such as `www.example.com`) into the
+numeric IP addresses (such as `192.0.2.1`) that computers use to
+connect to each other.
+
+A DNS record is created to point a (sub)domain to a certain location,
+which can be an IP address or another domain. In case you want to use
+GitLab Pages with your own (sub)domain, you need to access your domain's
+registrar control panel to add a DNS record pointing it back to your
+GitLab Pages site.
+
+Note that **how to** add DNS records depends on which server your domain
+is hosted on. Every control panel has its own place to do it. If you are
+not an admin of your domain, and don't have access to your registrar,
+you'll need to ask for the technical support of your hosting service
+to do it for you.
+
+To help you out, we've gathered some instructions on how to do that
+for the most popular hosting services:
+
+- [Amazon](http://docs.aws.amazon.com/gettingstarted/latest/swh/getting-started-configure-route53.html)
+- [Bluehost](https://my.bluehost.com/cgi/help/559)
+- [CloudFlare](https://support.cloudflare.com/hc/en-us/articles/200169096-How-do-I-add-A-records-)
+- [cPanel](https://documentation.cpanel.net/display/ALD/Edit+DNS+Zone)
+- [DreamHost](https://help.dreamhost.com/hc/en-us/articles/215414867-How-do-I-add-custom-DNS-records-)
+- [Go Daddy](https://www.godaddy.com/help/add-an-a-record-19238)
+- [Hostgator](http://support.hostgator.com/articles/changing-dns-records)
+- [Inmotion hosting](https://my.bluehost.com/cgi/help/559)
+- [Media Temple](https://mediatemple.net/community/products/dv/204403794/how-can-i-change-the-dns-records-for-my-domain)
+- [Microsoft](https://msdn.microsoft.com/en-us/library/bb727018.aspx)
+
+If your hosting service is not listed above, you can just try to
+search the web for "how to add dns record on <my hosting service>".
+
+#### DNS A record
+
+In case you want to point a root domain (`example.com`) to your
+GitLab Pages site, deployed to `namespace.gitlab.io`, you need to
+log into your domain's admin control panel and add a DNS `A` record
+pointing your domain to Pages' server IP address. For projects on
+GitLab.com, this IP is `104.208.235.32`. For projects leaving in
+other GitLab instances (CE or EE), please contact your sysadmin
+asking for this information (which IP address is Pages server
+running on your instance).
+
+**Practical Example:**
+
+![DNS A record pointing to GitLab.com Pages server](img/dns_a_record_example.png)
+
+#### DNS CNAME record
+
+In case you want to point a subdomain (`hello-world.example.com`)
+to your GitLab Pages site initially deployed to `namespace.gitlab.io`,
+you need to log into your domain's admin control panel and add a DNS
+`CNAME` record pointing your subdomain to your website URL
+(`namespace.gitlab.io`) address.
+
+Notice that, despite it's a user or project website, the `CNAME`
+should point to your Pages domain (`namespace.gitlab.io`),
+without any `/project-name`.
+
+**Practical Example:**
+
+![DNS CNAME record pointing to GitLab.com project](img/dns_cname_record_example.png)
+
+#### TL;DR
+
+| From | DNS Record | To |
+| ---- | ---------- | -- |
+| domain.com | A | 104.208.235.32 |
+| subdomain.domain.com | CNAME | namespace.gitlab.io |
+
+> **Notes**:
+>
+> - **Do not** use a CNAME record if you want to point your
+`domain.com` to your GitLab Pages site. Use an `A` record instead.
+> - **Do not** add any special chars after the default Pages
+domain. E.g., **do not** point your `subdomain.domain.com` to
+`namespace.gitlab.io.` or `namespace.gitlab.io/`.
+
+### SSL/TLS Certificates
+
+Every GitLab Pages project on GitLab.com will be available under
+HTTPS for the default Pages domain (`*.gitlab.io`). Once you set
+up your Pages project with your custom (sub)domain, if you want
+it secured by HTTPS, you will have to issue a certificate for that
+(sub)domain and install it on your project.
+
+>**Note:**
+Certificates are NOT required to add to your custom
+(sub)domain on your GitLab Pages project, though they are
+highly recommendable.
+
+The importance of having any website securely served under HTTPS
+is explained on the introductory section of the blog post
+[Secure GitLab Pages with StartSSL](https://about.gitlab.com/2016/06/24/secure-gitlab-pages-with-startssl/#https-a-quick-overview).
+
+The reason why certificates are so important is that they encrypt
+the connection between the **client** (you, me, your visitors)
+and the **server** (where you site lives), through a keychain of
+authentications and validations.
+
+### Issuing Certificates
+
+GitLab Pages accepts [PEM](https://support.quovadisglobal.com/kb/a37/what-is-pem-format.aspx) certificates issued by
+[Certificate Authorities (CA)](https://en.wikipedia.org/wiki/Certificate_authority)
+and self-signed certificates. Of course,
+[you'd rather issue a certificate than generate a self-signed](https://en.wikipedia.org/wiki/Self-signed_certificate),
+for security reasons and for having browsers trusting your
+site's certificate.
+
+There are several different kinds of certificates, each one
+with certain security level. A static personal website will
+not require the same security level as an online banking web app,
+for instance. There are a couple Certificate Authorities that
+offer free certificates, aiming to make the internet more secure
+to everyone. The most popular is [Let's Encrypt](https://letsencrypt.org/),
+which issues certificates trusted by most of browsers, it's open
+source, and free to use. Please read through this tutorial to
+understand [how to secure your GitLab Pages website with Let's Encrypt](https://about.gitlab.com/2016/04/11/tutorial-securing-your-gitlab-pages-with-tls-and-letsencrypt/).
+
+With the same popularity, there are [certificates issued by CloudFlare](https://www.cloudflare.com/ssl/),
+which also offers a [free CDN service](https://blog.cloudflare.com/cloudflares-free-cdn-and-you/).
+Their certs are valid up to 15 years. Read through the tutorial on
+[how to add a CloudFlare Certificate to your GitLab Pages website](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/).
+
+### Adding certificates to your project
+
+Regardless the CA you choose, the steps to add your certificate to
+your Pages project are the same.
+
+#### What do you need
+
+1. A PEM certificate
+1. An intermediate certificate
+1. A public key
+
+![Pages project - adding certificates](img/add_certificate_to_pages.png)
+
+These fields are found under your **Project**'s **Settings** > **Pages** > **New Domain**.
+
+#### What's what?
+
+- A PEM certificate is the certificate generated by the CA,
+which needs to be added to the field **Certificate (PEM)**.
+- An [intermediate certificate](https://en.wikipedia.org/wiki/Intermediate_certificate_authority) (aka "root certificate") is
+the part of the encryption keychain that identifies the CA.
+Usually it's combined with the PEM certificate, but there are
+some cases in which you need to add them manually.
+[CloudFlare certs](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/)
+are one of these cases.
+- A public key is an encrypted key which validates
+your PEM against your domain.
+
+#### Now what?
+
+Now that you hopefully understand why you need all
+of this, it's simple:
+
+- Your PEM certificate needs to be added to the first field
+- If your certificate is missing its intermediate, copy
+and paste the root certificate (usually available from your CA website)
+and paste it in the [same field as your PEM certificate](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/),
+just jumping a line between them.
+- Copy your public key and paste it in the last field
+
+>**Note:**
+**Do not** open certificates or encryption keys in
+regular text editors. Always use code editors (such as
+Sublime Text, Atom, Dreamweaver, Brackets, etc).
+
+|||
+|:--|--:|
+||[**Part 2: Quick start guide - Setting up GitLab Pages →**](getting_started_part_two.md)|
diff --git a/doc/pages/getting_started_part_three.md b/doc/pages/getting_started_part_three.md
new file mode 100644
index 00000000000..ef47abef3a0
--- /dev/null
+++ b/doc/pages/getting_started_part_three.md
@@ -0,0 +1,383 @@
+# GitLab Pages from A to Z: Part 3
+
+- _[Part 1: Static Sites, Domains, DNS Records, and SSL/TLS Certificates](getting_started_part_one.md)_
+- _[Part 2: Quick Start Guide - Setting Up GitLab Pages](getting_started_part_two.md)_
+- **Part 3: Creating and Tweaking `.gitlab-ci.yml` for GitLab Pages**
+
+---
+
+## Creating and Tweaking `.gitlab-ci.yml` for GitLab Pages
+
+[GitLab CI](https://about.gitlab.com/gitlab-ci/) serves
+numerous purposes, to build, test, and deploy your app
+from GitLab through
+[Continuous Integration, Continuous Delivery, and Continuous Deployment](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/)
+methods. You will need it to build your website with GitLab Pages,
+and deploy it to the Pages server.
+
+What this file actually does is telling the
+[GitLab Runner](https://docs.gitlab.com/runner/) to run scripts
+as you would do from the command line. The Runner acts as your
+terminal. GitLab CI tells the Runner which commands to run.
+Both are built-in in GitLab, and you don't need to set up
+anything for them to work.
+
+Explaining [every detail of GitLab CI](https://docs.gitlab.com/ce/ci/yaml/README.html)
+and GitLab Runner is out of the scope of this guide, but we'll
+need to understand just a few things to be able to write our own
+`.gitlab-ci.yml` or tweak an existing one. It's an
+[Yaml](http://docs.ansible.com/ansible/YAMLSyntax.html) file,
+with its own syntax. You can always check your CI syntax with
+the [GitLab CI Lint Tool](https://gitlab.com/ci/lint).
+
+**Practical Example:**
+
+Let's consider you have a [Jekyll](https://jekyllrb.com/) site.
+To build it locally, you would open your terminal, and run `jekyll build`.
+Of course, before building it, you had to install Jekyll in your computer.
+For that, you had to open your terminal and run `gem install jekyll`.
+Right? GitLab CI + GitLab Runner do the same thing. But you need to
+write in the `.gitlab-ci.yml` the script you want to run so
+GitLab Runner will do it for you. It looks more complicated then it
+is. What you need to tell the Runner:
+
+```
+$ gem install jekyll
+$ jekyll build
+```
+
+### Script
+
+To transpose this script to Yaml, it would be like this:
+
+```yaml
+script:
+ - gem install jekyll
+ - jekyll build
+```
+
+### Job
+
+So far so good. Now, each `script`, in GitLab is organized by
+a `job`, which is a bunch of scripts and settings you want to
+apply to that specific task.
+
+```yaml
+job:
+ script:
+ - gem install jekyll
+ - jekyll build
+```
+
+For GitLab Pages, this `job` has a specific name, called `pages`,
+which tells the Runner you want that task to deploy your website
+with GitLab Pages:
+
+```yaml
+pages:
+ script:
+ - gem install jekyll
+ - jekyll build
+```
+
+### The `public` directory
+
+We also need to tell Jekyll where do you want the website to build,
+and GitLab Pages will only consider files in a directory called `public`.
+To do that with Jekyll, we need to add a flag specifying the
+[destination (`-d`)](https://jekyllrb.com/docs/usage/) of the
+built website: `jekyll build -d public`. Of course, we need
+to tell this to our Runner:
+
+```yaml
+pages:
+ script:
+ - gem install jekyll
+ - jekyll build -d public
+```
+
+### Artifacts
+
+We also need to tell the Runner that this _job_ generates
+_artifacts_, which is the site built by Jekyll.
+Where are these artifacts stored? In the `public` directory:
+
+```yaml
+pages:
+ script:
+ - gem install jekyll
+ - jekyll build -d public
+ artifacts:
+ paths:
+ - public
+```
+
+The script above would be enough to build your Jekyll
+site with GitLab Pages. But, from Jekyll 3.4.0 on, its default
+template originated by `jekyll new project` requires
+[Bundler](http://bundler.io/) to install Jekyll dependencies
+and the default theme. To adjust our script to meet these new
+requirements, we only need to install and build Jekyll with Bundler:
+
+```yaml
+pages:
+ script:
+ - bundle install
+ - bundle exec jekyll build -d public
+ artifacts:
+ paths:
+ - public
+```
+
+That's it! A `.gitlab-ci.yml` with the content above would deploy
+your Jekyll 3.4.0 site with GitLab Pages. This is the minimum
+configuration for our example. On the steps below, we'll refine
+the script by adding extra options to our GitLab CI.
+
+### Image
+
+At this point, you probably ask yourself: "okay, but to install Jekyll
+I need Ruby. Where is Ruby on that script?". The answer is simple: the
+first thing GitLab Runner will look for in your `.gitlab-ci.yml` is a
+[Docker](https://www.docker.com/) image specifying what do you need in
+your container to run that script:
+
+```yaml
+image: ruby:2.3
+
+pages:
+ script:
+ - bundle install
+ - bundle exec jekyll build -d public
+ artifacts:
+ paths:
+ - public
+```
+
+In this case, you're telling the Runner to pull this image, which
+contains Ruby 2.3 as part of its file system. When you don't specify
+this image in your configuration, the Runner will use a default
+image, which is Ruby 2.1.
+
+If your SSG needs [NodeJS](https://nodejs.org/) to build, you'll
+need to specify which image you want to use, and this image should
+contain NodeJS as part of its file system. E.g., for a
+[Hexo](https://gitlab.com/pages/hexo) site, you can use `image: node:4.2.2`.
+
+>**Note:**
+We're not trying to explain what a Docker image is,
+we just need to introduce the concept with a minimum viable
+explanation. To know more about Docker images, please visit
+their website or take a look at a
+[summarized explanation](http://paislee.io/how-to-automate-docker-deployments/) here.
+
+Let's go a little further.
+
+### Branching
+
+If you use GitLab as a version control platform, you will have your
+branching strategy to work on your project. Meaning, you will have
+other branches in your project, but you'll want only pushes to the
+default branch (usually `master`) to be deployed to your website.
+To do that, we need to add another line to our CI, telling the Runner
+to only perform that _job_ called `pages` on the `master` branch `only`:
+
+```yaml
+image: ruby:2.3
+
+pages:
+ script:
+ - bundle install
+ - bundle exec jekyll build -d public
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
+```
+
+### Stages
+
+Another interesting concept to keep in mind are build stages.
+Your web app can pass through a lot of tests and other tasks
+until it's deployed to staging or production environments.
+There are three default stages on GitLab CI: build, test,
+and deploy. To specify which stage your _job_ is running,
+simply add another line to your CI:
+
+```yaml
+image: ruby:2.3
+
+pages:
+ stage: deploy
+ script:
+ - bundle install
+ - bundle exec jekyll build -d public
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
+```
+
+You might ask yourself: "why should I bother with stages
+at all?" Well, let's say you want to be able to test your
+script and check the built site before deploying your site
+to production. You want to run the test exactly as your
+script will do when you push to `master`. It's simple,
+let's add another task (_job_) to our CI, telling it to
+test every push to other branches, `except` the `master` branch:
+
+```yaml
+image: ruby:2.3
+
+pages:
+ stage: deploy
+ script:
+ - bundle install
+ - bundle exec jekyll build -d public
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
+
+test:
+ stage: test
+ script:
+ - bundle install
+ - bundle exec jekyll build -d test
+ artifacts:
+ paths:
+ - test
+ except:
+ - master
+```
+
+The `test` job is running on the stage `test`, Jekyll
+will build the site in a directory called `test`, and
+this job will affect all the branches except `master`.
+
+The best benefit of applying _stages_ to different
+_jobs_ is that every job in the same stage builds in
+parallel. So, if your web app needs more than one test
+before being deployed, you can run all your test at the
+same time, it's not necessary to wait one test to finish
+to run the other. Of course, this is just a brief
+introduction of GitLab CI and GitLab Runner, which are
+tools much more powerful than that. This is what you
+need to be able to create and tweak your builds for
+your GitLab Pages site.
+
+### Before Script
+
+To avoid running the same script multiple times across
+your _jobs_, you can add the parameter `before_script`,
+in which you specify which commands you want to run for
+every single _job_. In our example, notice that we run
+`bundle install` for both jobs, `pages` and `test`.
+We don't need to repeat it:
+
+```yaml
+image: ruby:2.3
+
+before_script:
+ - bundle install
+
+pages:
+ stage: deploy
+ script:
+ - bundle exec jekyll build -d public
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
+
+test:
+ stage: test
+ script:
+ - bundle exec jekyll build -d test
+ artifacts:
+ paths:
+ - test
+ except:
+ - master
+```
+
+### Caching Dependencies
+
+If you want to cache the installation files for your
+projects dependencies, for building faster, you can
+use the parameter `cache`. For this example, we'll
+cache Jekyll dependencies in a `vendor` directory
+when we run `bundle install`:
+
+```yaml
+image: ruby:2.3
+
+cache:
+ paths:
+ - vendor/
+
+before_script:
+ - bundle install --path vendor
+
+pages:
+ stage: deploy
+ script:
+ - bundle exec jekyll build -d public
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
+
+test:
+ stage: test
+ script:
+ - bundle exec jekyll build -d test
+ artifacts:
+ paths:
+ - test
+ except:
+ - master
+```
+
+For this specific case, we need to exclude `/vendor`
+from Jekyll `_config.yml` file, otherwise Jekyll will
+understand it as a regular directory to build
+together with the site:
+
+```yml
+exclude:
+ - vendor
+```
+
+There we go! Now our GitLab CI not only builds our website,
+but also **continuously test** pushes to feature-branches,
+**caches** dependencies installed with Bundler, and
+**continuously deploy** every push to the `master` branch.
+
+## Advanced GitLab CI for GitLab Pages
+
+What you can do with GitLab CI is pretty much up to your
+creativity. Once you get used to it, you start creating
+awesome scripts that automate most of tasks you'd do
+manually in the past. Read through the
+[documentation of GitLab CI](https://docs.gitlab.com/ce/ci/yaml/README.html)
+to understand how to go even further on your scripts.
+
+- On this blog post, understand the concept of
+[using GitLab CI `environments` to deploy your
+web app to staging and production](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/).
+- On this post, learn [how to run jobs sequentially,
+in parallel, or build a custom pipeline](https://about.gitlab.com/2016/07/29/the-basics-of-gitlab-ci/)
+- On this blog post, we go through the process of
+[pulling specific directories from different projects](https://about.gitlab.com/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/)
+to deploy this website you're looking at, docs.gitlab.com.
+- On this blog post, we teach you [how to use GitLab Pages to produce a code coverage report](https://about.gitlab.com/2016/11/03/publish-code-coverage-report-with-gitlab-pages/).
+
+|||
+|:--|--:|
+|[**← Part 2: Quick start guide - Setting up GitLab Pages**](getting_started_part_two.md)||
diff --git a/doc/pages/getting_started_part_two.md b/doc/pages/getting_started_part_two.md
new file mode 100644
index 00000000000..07dd24122c4
--- /dev/null
+++ b/doc/pages/getting_started_part_two.md
@@ -0,0 +1,152 @@
+# GitLab Pages from A to Z: Part 2
+
+> Type: user guide
+>
+> Level: beginner
+
+- _[Part 1: Static Sites, Domains, DNS Records, and SSL/TLS Certificates](getting_started_part_one.md)_
+- **Part 2: Quick Start Guide - Setting Up GitLab Pages**
+- _[Part 3: Creating and Tweaking `.gitlab-ci.yml` for GitLab Pages](getting_started_part_three.md)_
+
+----
+
+## Setting up GitLab Pages
+
+For a complete step-by-step tutorial, please read the
+blog post [Hosting on GitLab.com with GitLab Pages](https://about.gitlab.com/2016/04/07/gitlab-pages-setup/). The following sections will explain
+what do you need and why do you need them.
+
+## What you need to get started
+
+1. A project
+1. A configuration file (`.gitlab-ci.yml`) to deploy your site
+1. A specific `job` called `pages` in the configuration file
+that will make GitLab aware that you are deploying a GitLab Pages website
+
+Optional Features:
+
+1. A custom domain or subdomain
+1. A DNS pointing your (sub)domain to your Pages site
+ 1. **Optional**: an SSL/TLS certificate so your custom
+ domain is accessible under HTTPS.
+
+## Project
+
+Your GitLab Pages project is a regular project created the
+same way you do for the other ones. To get started with GitLab Pages, you have two ways:
+
+- Fork one of the templates from Page Examples, or
+- Create a new project from scratch
+
+Let's go over both options.
+
+### Fork a project to get started from
+
+To make things easy for you, we've created this
+[group](https://gitlab.com/pages) of default projects
+containing the most popular SSGs templates.
+
+Watch the [video tutorial](https://youtu.be/TWqh9MtT4Bg) we've
+created for the steps below.
+
+1. Choose your SSG template
+1. Fork a project from the [Pages group](https://gitlab.com/pages)
+1. Remove the fork relationship by navigating to your **Project**'s **Settings** > **Edit Project**
+
+ ![remove fork relashionship](img/remove_fork_relashionship.png)
+
+1. Enable Shared Runners for your fork: navigate to your **Project**'s **Settings** > **CI/CD Pipelines**
+1. Trigger a build (push a change to any file)
+1. As soon as the build passes, your website will have been deployed with GitLab Pages. Your website URL will be available under your **Project**'s **Settings** > **Pages**
+
+To turn a **project website** forked from the Pages group into a **user/group** website, you'll need to:
+
+- Rename it to `namespace.gitlab.io`: navigate to **Project**'s **Settings** > **Edit Project** > **Rename repository**
+- Adjust your SSG's [base URL](#urls-and-baseurls) to from `"project-name"` to `""`. This setting will be at a different place for each SSG, as each of them have their own structure and file tree. Most likelly, it will be in the SSG's config file.
+
+> **Notes:**
+>
+>1. Why do I need to remove the fork relationship?
+>
+> Unless you want to contribute to the original project,
+you won't need it connected to the upstream. A
+[fork](https://about.gitlab.com/2016/12/01/how-to-keep-your-fork-up-to-date-with-its-origin/#fork)
+is useful for submitting merge requests to the upstream.
+>
+> 2. Why do I need to enable Shared Runners?
+>
+> Shared Runners will run the script set by your GitLab CI
+configuration file. They're enabled by default to new projects,
+but not to forks.
+
+### Create a project from scratch
+
+1. From your **Project**'s **[Dashboard](https://gitlab.com/dashboard/projects)**,
+click **New project**, and name it considering the
+[practical examples](getting_started_part_one.md#practical-examples).
+1. Clone it to your local computer, add your website
+files to your project, add, commit and push to GitLab.
+1. From the your **Project**'s page, click **Set up CI**:
+
+ ![setup GitLab CI](img/setup_ci.png)
+
+1. Choose one of the templates from the dropbox menu.
+Pick up the template corresponding to the SSG you're using (or plain HTML).
+
+ ![gitlab-ci templates](img/choose_ci_template.png)
+
+Once you have both site files and `.gitlab-ci.yml` in your project's
+root, GitLab CI will build your site and deploy it with Pages.
+Once the first build passes, you see your site is live by
+navigating to your **Project**'s **Settings** > **Pages**,
+where you'll find its default URL.
+
+> **Notes:**
+>
+> - GitLab Pages [supports any SSG](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/), but,
+if you don't find yours among the templates, you'll need
+to configure your own `.gitlab-ci.yml`. Do do that, please
+read through the article [Creating and Tweaking `.gitlab-ci.yml` for GitLab Pages](getting_started_part_three.md). New SSGs are very welcome among
+the [example projects](https://gitlab.com/pages). If you set
+up a new one, please
+[contribute](https://gitlab.com/pages/pages.gitlab.io/blob/master/CONTRIBUTING.md)
+to our examples.
+>
+> - The second step _"Clone it to your local computer"_, can be done
+differently, achieving the same results: instead of cloning the bare
+repository to you local computer and moving your site files into it,
+you can run `git init` in your local website directory, add the
+remote URL: `git remote add origin git@gitlab.com:namespace/project-name.git`,
+then add, commit, and push.
+
+### URLs and Baseurls
+
+Every Static Site Generator (SSG) default configuration expects
+to find your website under a (sub)domain (`example.com`), not
+in a subdirectory of that domain (`example.com/subdir`). Therefore,
+whenever you publish a project website (`namespace.gitlab.io/project-name`),
+you'll have to look for this configuration (base URL) on your SSG's
+documentation and set it up to reflect this pattern.
+
+For example, for a Jekyll site, the `baseurl` is defined in the Jekyll
+configuration file, `_config.yml`. If your website URL is
+`https://john.gitlab.io/blog/`, you need to add this line to `_config.yml`:
+
+```yaml
+baseurl: "/blog"
+```
+
+On the contrary, if you deploy your website after forking one of
+our [default examples](https://gitlab.com/pages), the baseurl will
+already be configured this way, as all examples there are project
+websites. If you decide to make yours a user or group website, you'll
+have to remove this configuration from your project. For the Jekyll
+example we've just mentioned, you'd have to change Jekyll's `_config.yml` to:
+
+```yaml
+baseurl: ""
+```
+
+|||
+|:--|--:|
+|[**← Part 1: Static sites, domains, DNS records, and SSL/TLS certificates**](getting_started_part_one.md)|[**Part 3: Creating and tweaking `.gitlab-ci.yml` for GitLab Pages →**](getting_started_part_three.md)|
diff --git a/doc/pages/img/add_certificate_to_pages.png b/doc/pages/img/add_certificate_to_pages.png
new file mode 100644
index 00000000000..d92a981dc60
--- /dev/null
+++ b/doc/pages/img/add_certificate_to_pages.png
Binary files differ
diff --git a/doc/pages/img/choose_ci_template.png b/doc/pages/img/choose_ci_template.png
new file mode 100644
index 00000000000..0697542abc8
--- /dev/null
+++ b/doc/pages/img/choose_ci_template.png
Binary files differ
diff --git a/doc/pages/img/dns_a_record_example.png b/doc/pages/img/dns_a_record_example.png
new file mode 100644
index 00000000000..b923730388a
--- /dev/null
+++ b/doc/pages/img/dns_a_record_example.png
Binary files differ
diff --git a/doc/pages/img/dns_cname_record_example.png b/doc/pages/img/dns_cname_record_example.png
new file mode 100644
index 00000000000..d64a843a283
--- /dev/null
+++ b/doc/pages/img/dns_cname_record_example.png
Binary files differ
diff --git a/doc/pages/img/remove_fork_relashionship.png b/doc/pages/img/remove_fork_relashionship.png
new file mode 100644
index 00000000000..f5b5e543f21
--- /dev/null
+++ b/doc/pages/img/remove_fork_relashionship.png
Binary files differ
diff --git a/doc/pages/img/setup_ci.png b/doc/pages/img/setup_ci.png
new file mode 100644
index 00000000000..7ce0431f4d4
--- /dev/null
+++ b/doc/pages/img/setup_ci.png
Binary files differ
diff --git a/doc/pages/index.md b/doc/pages/index.md
new file mode 100644
index 00000000000..a6f928cc243
--- /dev/null
+++ b/doc/pages/index.md
@@ -0,0 +1,49 @@
+# All you need to know about GitLab Pages
+
+With GitLab Pages you can create static websites for your GitLab projects,
+groups, or user accounts. You can use any static website generator: Jekyll,
+Middleman, Hexo, Hugo, Pelican, you name it! Connect as many customs domains
+as you like and bring your own TLS certificate to secure them.
+
+Here's some info we have gathered to get you started.
+
+## General info
+
+- [Product webpage](https://pages.gitlab.io)
+- [We're bringing GitLab Pages to CE](https://about.gitlab.com/2016/12/24/were-bringing-gitlab-pages-to-community-edition/)
+- [Pages group - templates](https://gitlab.com/pages)
+
+## Getting started
+
+- GitLab Pages from A to Z
+ - [Part 1: Static sites, domains, DNS records, and SSL/TLS certificates](getting_started_part_one.md)
+ - [Part 2: Quick start guide - Setting up GitLab Pages](getting_started_part_two.md)
+ - [Part 3: Creating and tweaking `.gitlab-ci.yml` for GitLab Pages](getting_started_part_three.md)
+- [Hosting on GitLab.com with GitLab Pages](https://about.gitlab.com/2016/04/07/gitlab-pages-setup/) a comprehensive step-by-step guide
+- Secure GitLab Pages custom domain with SSL/TLS certificates
+ - [Let's Encrypt](https://about.gitlab.com/2016/04/11/tutorial-securing-your-gitlab-pages-with-tls-and-letsencrypt/)
+ - [CloudFlare](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/)
+ - [StartSSL](https://about.gitlab.com/2016/06/24/secure-gitlab-pages-with-startssl/)
+- Static Site Generators - Blog posts series
+ - [SSGs part 1: Static vs dynamic websites](https://about.gitlab.com/2016/06/03/ssg-overview-gitlab-pages-part-1-dynamic-x-static/)
+ - [SSGs part 2: Modern static site generators](https://about.gitlab.com/2016/06/10/ssg-overview-gitlab-pages-part-2/)
+ - [SSGs part 3: Build any SSG site with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/)
+- [Posting to your GitLab Pages blog from iOS](https://about.gitlab.com/2016/08/19/posting-to-your-gitlab-pages-blog-from-ios/)
+
+## Video tutorials
+
+- [How to publish a website with GitLab Pages on GitLab.com: from a forked project](https://youtu.be/TWqh9MtT4Bg)
+- [How to Enable GitLab Pages for GitLab CE and EE](https://youtu.be/dD8c7WNcc6s)
+
+## Advanced use
+
+- Blog Posts:
+ - [GitLab CI: Run jobs sequentially, in parallel, or build a custom pipeline](https://about.gitlab.com/2016/07/29/the-basics-of-gitlab-ci/)
+ - [GitLab CI: Deployment & environments](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/)
+ - [Building a new GitLab docs site with Nanoc, GitLab CI, and GitLab Pages](https://about.gitlab.com/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/)
+ - [Publish code coverage reports with GitLab Pages](https://about.gitlab.com/2016/11/03/publish-code-coverage-report-with-gitlab-pages/)
+
+## Specific documentation
+
+- [User docs](../user/project/pages/index.md)
+- [Admin docs](../administration/pages/index.md)
diff --git a/doc/profile/preferences.md b/doc/profile/preferences.md
index 073b8797508..4f2b00f3dd1 100644
--- a/doc/profile/preferences.md
+++ b/doc/profile/preferences.md
@@ -3,13 +3,6 @@
Settings in the **Profile > Preferences** page allow the user to customize
various aspects of the site to their liking.
-## Application theme
-
-Changing this setting allows the user to customize the color scheme used for the
-navigation bar on the left side of the screen.
-
-The default is **Charcoal**.
-
## Syntax highlighting theme
_GitLab uses the [rouge ruby library][rouge] for syntax highlighting. For a
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 0fb69d63dbe..a5b8cd6455c 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -44,8 +44,8 @@ environment variable `SKIP`. You can skip:
- `db` (database)
- `uploads` (attachments)
- `repositories` (Git repositories data)
-- `builds` (CI build output logs)
-- `artifacts` (CI build artifacts)
+- `builds` (CI job output logs)
+- `artifacts` (CI job artifacts)
- `lfs` (LFS objects)
- `registry` (Container Registry images)
@@ -84,6 +84,28 @@ Deleting tmp directories...[DONE]
Deleting old backups... [SKIPPING]
```
+## Backup Strategy Option
+
+> **Note:** Introduced as an option in 8.17
+
+The default backup strategy is to essentially stream data from the respective
+data locations to the backup using the Linux command `tar` and `gzip`. This works
+fine in most cases, but can cause problems when data is rapidly changing.
+
+When data changes while `tar` is reading it, the error `file changed as we read
+it` may occur, and will cause the backup process to fail. To combat this, 8.17
+introduces a new backup strategy called `copy`. The strategy copies data files
+to a temporary location before calling `tar` and `gzip`, avoiding the error.
+
+A side-effect is that the backup process with take up to an additional 1X disk
+space. The process does its best to clean up the temporary files at each stage
+so the problem doesn't compound, but it could be a considerable change for large
+installations. This is why the `copy` strategy is not the default in 8.17.
+
+To use the `copy` strategy instead of the default streaming strategy, specify
+`STRATEGY=copy` in the Rake task command. For example,
+`sudo gitlab-rake gitlab:backup:create STRATEGY=copy`.
+
## Exclude specific directories from the backup
You can choose what should be backed up by adding the environment variable `SKIP`.
diff --git a/doc/university/README.md b/doc/university/README.md
index e9f14703789..8d4e7eff115 100644
--- a/doc/university/README.md
+++ b/doc/university/README.md
@@ -5,7 +5,7 @@ GitLab University is the best place to learn about **Version Control with Git an
It doesn't replace, but accompanies our great [Documentation](https://docs.gitlab.com)
and [Blog Articles](https://about.gitlab.com/blog/).
-Would you like to contribute to GitLab University? Then please take a look at our contribution [process](process) for more information.
+Would you like to contribute to GitLab University? Then please take a look at our contribution [process](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/PROCESS.md) for more information.
## Gitlab University Curriculum
diff --git a/doc/university/glossary/README.md b/doc/university/glossary/README.md
index 979a1c5d310..ec565c3e7bf 100644
--- a/doc/university/glossary/README.md
+++ b/doc/university/glossary/README.md
@@ -30,15 +30,15 @@ A version control [system](https://www.jfrog.com/open-source/#os-arti) for non-t
### Artifacts
-Objects (usually binary and large) created by a build process. These can include use cases, class diagrams, requirements and design documents.
+Objects (usually binary and large) created by a build process. These can include use cases, class diagrams, requirements and design documents.
### Atlassian
-A [company](https://www.atlassian.com) that develops software products for developers and project managers including Bitbucket, Jira, Hipchat, Confluence, Bamboo.
+A [company](https://www.atlassian.com) that develops software products for developers and project managers including Bitbucket, Jira, Hipchat, Confluence, Bamboo.
### Audit Log
-Also called an [audit trail](https://en.wikipedia.org/wiki/Audit_trail), an audit log is a document that records an event in an IT system.
+Also called an [audit trail](https://en.wikipedia.org/wiki/Audit_trail), an audit log is a document that records an event in an IT system.
### Auto Defined User Group
@@ -55,7 +55,7 @@ Entry level [subscription](https://about.gitlab.com/pricing/) for GitLab EE curr
### Bitbucket
-Atlassian's web hosting service for Git and Mercurial Projects. Read about [migrating](https://docs.gitlab.com/ce/workflow/importing/import_projects_from_bitbucket.html) from BitBucket to a GitLab instance.
+Atlassian's web hosting service for Git and Mercurial Projects. Read about [migrating](https://docs.gitlab.com/ce/workflow/importing/import_projects_from_bitbucket.html) from BitBucket to a GitLab instance.
### Branch
@@ -65,8 +65,8 @@ A branch is a parallel version of a repository. This allows you to work on the r
Having your own logo on [your GitLab instance login page](https://docs.gitlab.com/ee/customization/branded_login_page.html) instead of the GitLab logo.
-### Build triggers
-These protect your code base against breaks, for instance when a team is working on the same project. Learn about [setting up](https://docs.gitlab.com/ce/ci/triggers/README.html) build triggers.
+### Job triggers
+These protect your code base against breaks, for instance when a team is working on the same project. Learn about [setting up](https://docs.gitlab.com/ce/ci/triggers/README.html) job triggers.
### CEPH
@@ -74,7 +74,7 @@ These protect your code base against breaks, for instance when a team is working
### ChatOps
-The ability to [initiate an action](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/1412) from chat. ChatBots run in your chat application and give you the ability to do "anything" from chat.
+The ability to [initiate an action](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/1412) from chat. ChatBots run in your chat application and give you the ability to do "anything" from chat.
### Clone
@@ -82,7 +82,7 @@ A [copy](https://git-scm.com/docs/git-clone) of a repository stored on your mach
### Code Review
-Examination of a progam's code. The main aim is to maintain high quality standards of code that is being shipped. Merge requests [serve as a code review tool](https://about.gitlab.com/2014/09/29/gitlab-flow/) in GitLab.
+Examination of a progam's code. The main aim is to maintain high quality standards of code that is being shipped. Merge requests [serve as a code review tool](https://about.gitlab.com/2014/09/29/gitlab-flow/) in GitLab.
### Code Snippet
@@ -140,7 +140,7 @@ A [SSH key](https://docs.gitlab.com/ce/gitlab-basics/create-your-ssh-keys.html)s
For us at GitLab, this means a software developer, or someone who makes software. It is also one of the levels of access in our multi-level approval system.
-### DevOps
+### DevOps
The intersection of software engineering, quality assurance, and technology operations. Explore more DevOps topics in the [glossary by XebiaLabs](https://xebialabs.com/glossary/)
@@ -160,7 +160,7 @@ A [feature](https://docs.gitlab.com/ce/user/project/container_registry.html) of
### ElasticSearch
-Elasticsearch is a flexible, scalable and powerful search service. When [enabled](https://gitlab.com/help/integration/elasticsearch.md), it helps keep GitLab's search fast when dealing with a huge amount of data.
+Elasticsearch is a flexible, scalable and powerful search service. When [enabled](https://gitlab.com/help/integration/elasticsearch.md), it helps keep GitLab's search fast when dealing with a huge amount of data.
### Emacs
@@ -174,7 +174,7 @@ A code review [tool](https://www.gerritcodereview.com/) built on top of Git.
### Git Attributes
-A [git attributes file](https://git-scm.com/docs/gitattributes) is a simple text file that gives attributes to pathnames.
+A [git attributes file](https://git-scm.com/docs/gitattributes) is a simple text file that gives attributes to pathnames.
### Git Hooks
@@ -209,7 +209,7 @@ Our free SaaS for public and private repositories.
Allows you to replicate your GitLab instance to other geographical locations as a read-only fully operational version. It [can be used](https://docs.gitlab.com/ee/gitlab-geo/README.html) for cloning and fetching projects, in addition to reading any data. This will make working with large repositories over large distances much faster.
### GitLab Pages
-These allow you to [create websites](https://gitlab.com/help/pages/README.md) for your GitLab projects, groups, or user account.
+These allow you to [create websites](https://gitlab.com/help/pages/README.md) for your GitLab projects, groups, or user account.
### Gitolite
@@ -253,7 +253,7 @@ A [tool](https://docs.gitlab.com/ee/integration/external-issue-tracker.html) use
### Jenkins
-An Open Source CI tool written using the Java programming language. [Jenkins](https://jenkins-ci.org/) does the same job as GitLab CI, Bamboo, and Travis CI. It is extremely popular.
+An Open Source CI tool written using the Java programming language. [Jenkins](https://jenkins-ci.org/) does the same job as GitLab CI, Bamboo, and Travis CI. It is extremely popular.
### Jira
@@ -289,7 +289,7 @@ Allows you to synchronize the members of a GitLab group with one or more LDAP gr
### Load Balancer
-A [device](https://en.wikipedia.org/wiki/Load_balancing_(computing)) that distributes network or application traffic across multiple servers.
+A [device](https://en.wikipedia.org/wiki/Load_balancing_(computing)) that distributes network or application traffic across multiple servers.
### Git Large File Storage (LFS)
@@ -301,7 +301,7 @@ An operating system like Windows or OS X. It is mostly used by software develope
### Markdown
-A lightweight markup language with plain text formatting syntax designed so that it can be converted to HTML and many other formats using a tool by the same name. Markdown is often used to format readme files, for writing messages in online discussion forums, and to create rich text using a plain text editor. Checkout GitLab's [Markdown guide](https://gitlab.com/help/user/markdown.md).
+A lightweight markup language with plain text formatting syntax designed so that it can be converted to HTML and many other formats using a tool by the same name. Markdown is often used to format readme files, for writing messages in online discussion forums, and to create rich text using a plain text editor. Checkout GitLab's [Markdown guide](https://gitlab.com/help/user/markdown.md).
### Maria DB
@@ -313,11 +313,11 @@ Name of the [default branch](https://git-scm.com/book/en/v1/Git-Branching-What-a
### Mattermost
-An open source, self-hosted messaging alternative to Slack. View GitLab's Mattermost [feature](https://gitlab.com/gitlab-org/gitlab-mattermost).
+An open source, self-hosted messaging alternative to Slack. View GitLab's Mattermost [feature](https://gitlab.com/gitlab-org/gitlab-mattermost).
### Mercurial
-A free distributed version control system similar to and a competitor with Git.
+A free distributed version control system similar to and a competitor with Git.
### Merge
@@ -325,7 +325,7 @@ Takes changes from one branch, and [applies them](https://git-scm.com/docs/git-m
### Merge Conflict
-[Arises](https://about.gitlab.com/2016/09/06/resolving-merge-conflicts-from-the-gitlab-ui/) when a merge can't be performed cleanly between two versions of the same file.
+[Arises](https://about.gitlab.com/2016/09/06/resolving-merge-conflicts-from-the-gitlab-ui/) when a merge can't be performed cleanly between two versions of the same file.
### Meteor
@@ -345,7 +345,7 @@ A type of software license. It lets people do anything with your code with prope
### Mondo Rescue
-A free disaster recovery [software](https://help.ubuntu.com/community/MondoMindi).
+A free disaster recovery [software](https://help.ubuntu.com/community/MondoMindi).
### MySQL
@@ -361,7 +361,7 @@ A web [server](https://www.nginx.com/resources/wiki/) (pronounced "engine x"). I
### OAuth
-An open standard for authorization, commonly used as a way for internet users to log into third party websites using their Microsoft, Google, Facebook or Twitter accounts without exposing their password. GitLab [is](https://docs.gitlab.com/ce/integration/oauth_provider.html) an OAuth2 authentication service provider.
+An open standard for authorization, commonly used as a way for internet users to log into third party websites using their Microsoft, Google, Facebook or Twitter accounts without exposing their password. GitLab [is](https://docs.gitlab.com/ce/integration/oauth_provider.html) an OAuth2 authentication service provider.
### Omnibus Packages
@@ -371,13 +371,13 @@ A way to [package different services and tools](https://docs.gitlab.com/omnibus/
On your own server. In GitLab, this [refers](https://about.gitlab.com/2015/02/12/why-ship-on-premises-in-the-saas-era/) to the ability to download GitLab EE/GitLab CE and host it on your own server rather than using GitLab.com, which is hosted by GitLab Inc's servers.
-### Open Core
+### Open Core
GitLab's [business model](https://about.gitlab.com/2016/07/20/gitlab-is-open-core-github-is-closed-source/). Coined by Andrew Lampitt in 2008, the [open core model](https://en.wikipedia.org/wiki/Open_core) primarily involves offering a "core" or feature-limited version of a software product as free and open-source software, while offering "commercial" versions or add-ons as proprietary software.
### Open Source Software
-Software for which the original source code is freely [available](https://opensource.org/docs/osd) and may be redistributed and modified. GitLab prioritizes open source [stewardship](https://about.gitlab.com/2016/01/11/being-a-good-open-source-steward/).
+Software for which the original source code is freely [available](https://opensource.org/docs/osd) and may be redistributed and modified. GitLab prioritizes open source [stewardship](https://about.gitlab.com/2016/01/11/being-a-good-open-source-steward/).
### Owner
@@ -405,7 +405,7 @@ GitLab Premium EE [subscription](https://about.gitlab.com/pricing/) that include
### PostgreSQL
-An [object-relational](https://en.wikipedia.org/wiki/PostgreSQL) database. Touted as the most advanced open source database, it is one of two database management systems [supported by](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/database.md) GitLab, the other being MySQL.
+An [object-relational](https://en.wikipedia.org/wiki/PostgreSQL) database. Touted as the most advanced open source database, it is one of two database management systems [supported by](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/database.md) GitLab, the other being MySQL.
### Protected Branches
@@ -421,7 +421,7 @@ A popular DevOps [automation tool](https://puppet.com/product/how-puppet-works).
### Push
-Git [command](https://git-scm.com/docs/git-push) to send commits from the local repository to the remote repository. Read about [advanced push rules](https://gitlab.com/help/pages/README.md) in GitLab.
+Git [command](https://git-scm.com/docs/git-push) to send commits from the local repository to the remote repository. Read about [advanced push rules](https://gitlab.com/help/pages/README.md) in GitLab.
### RE Read Only
@@ -429,7 +429,7 @@ Permissions to see a file and its contents, but not change it.
### Rebase
-In addition to the merge, the [rebase](https://git-scm.com/book/en/v2/Git-Branching-Rebasing) is a main way to integrate changes from one branch into another.
+In addition to the merge, the [rebase](https://git-scm.com/book/en/v2/Git-Branching-Rebasing) is a main way to integrate changes from one branch into another.
### (Git) Repository
@@ -449,7 +449,7 @@ An open source chat application for teams, RocketChat is very similar to Slack b
### Route Table
-A route table contains rules (called routes) that determine where network traffic is directed. Each [subnet in a VPC](http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Route_Tables.html) must be associated with a route table.
+A route table contains rules (called routes) that determine where network traffic is directed. Each [subnet in a VPC](http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Route_Tables.html) must be associated with a route table.
### Runners
@@ -477,15 +477,15 @@ The board used to track the status and progress of each of the sprint backlog it
### Shell
-Terminal on Mac OSX, GitBash on Windows, or Linux Terminal on Linux. You [use git]() and make changes to GitLab projects in your shell. You [use git](https://docs.gitlab.com/ce/gitlab-basics/start-using-git.html) and make changes to GitLab projects in your shell.
+Terminal on Mac OSX, GitBash on Windows, or Linux Terminal on Linux. You [use git]() and make changes to GitLab projects in your shell. You [use git](https://docs.gitlab.com/ce/gitlab-basics/start-using-git.html) and make changes to GitLab projects in your shell.
### Single-tenant
-The tenant purchases their own copy of the software and the software can be customized to meet the specific and needs of that customer. [GitHost.io](https://about.gitlab.com/handbook/positioning-faq/) is our provider of single-tenant 'managed cloud' GitLab instances.
+The tenant purchases their own copy of the software and the software can be customized to meet the specific and needs of that customer. [GitHost.io](https://about.gitlab.com/handbook/positioning-faq/) is our provider of single-tenant 'managed cloud' GitLab instances.
### Slack
-Real time messaging app for teams that is used internally by GitLab team members. GitLab users can enable [Slack integration](https://docs.gitlab.com/ce/project_services/slack.html) to trigger push, issue, and merge request events among others.
+Real time messaging app for teams that is used internally by GitLab team members. GitLab users can enable [Slack integration](https://docs.gitlab.com/ce/project_services/slack.html) to trigger push, issue, and merge request events among others.
### Slave Servers
@@ -529,7 +529,7 @@ A program that allows you to perform superuser/administrator actions on Unix Ope
### Subversion (SVN)
-An open source version control system. Read about [migrating from SVN](https://docs.gitlab.com/ce/workflow/importing/migrating_from_svn.html) to GitLab using SubGit.
+An open source version control system. Read about [migrating from SVN](https://docs.gitlab.com/ce/workflow/importing/migrating_from_svn.html) to GitLab using SubGit.
### Tag
@@ -545,7 +545,7 @@ An open source project management and bug tracking web [application](https://tra
### Untracked files
-New files that Git has not [been told](https://git-scm.com/book/en/v2/Git-Basics-Recording-Changes-to-the-Repository) to track previously.
+New files that Git has not [been told](https://git-scm.com/book/en/v2/Git-Basics-Recording-Changes-to-the-Repository) to track previously.
### User
@@ -553,11 +553,11 @@ Anyone interacting with the software.
### Version Control Software (VCS)
-Version control is a system that records changes to a file or set of files over time so that you can recall specific versions later. VCS [has evolved](https://docs.google.com/presentation/d/16sX7hUrCZyOFbpvnrAFrg6tVO5_yT98IgdAqOmXwBho/edit#slide=id.gd69537a19_0_32) from local version control systems, to centralized version control systems, to the present distributed version control systems like Git, Mercurial, Bazaar, and Darcs.
+Version control is a system that records changes to a file or set of files over time so that you can recall specific versions later. VCS [has evolved](https://docs.google.com/presentation/d/16sX7hUrCZyOFbpvnrAFrg6tVO5_yT98IgdAqOmXwBho/edit#slide=id.gd69537a19_0_32) from local version control systems, to centralized version control systems, to the present distributed version control systems like Git, Mercurial, Bazaar, and Darcs.
### Virtual Private Cloud (VPC)
-An on demand configurable pool of shared computing resources allocated within a public cloud environment, providing some isolation between the different users using the resources. GitLab users need to create a new Amazon VPC in order to [setup High Availability](https://docs.gitlab.com/ce/university/high-availability/aws/).
+An on demand configurable pool of shared computing resources allocated within a public cloud environment, providing some isolation between the different users using the resources. GitLab users need to create a new Amazon VPC in order to [setup High Availability](https://docs.gitlab.com/ce/university/high-availability/aws/).
### Virtual private server (VPS)
@@ -565,15 +565,15 @@ A [virtual machine](https://en.wikipedia.org/wiki/Virtual_private_server) sold a
### VM Instance
-In object-oriented programming, an [instance](http://stackoverflow.com/questions/20461907/what-is-meaning-of-instance-in-programming) is a specific realization of any object. An object may be varied in a number of ways. Each realized variation of that object is an instance. Therefore, a VM instance is an instance of a virtual machine, which is an emulation of a computer system.
+In object-oriented programming, an [instance](http://stackoverflow.com/questions/20461907/what-is-meaning-of-instance-in-programming) is a specific realization of any object. An object may be varied in a number of ways. Each realized variation of that object is an instance. Therefore, a VM instance is an instance of a virtual machine, which is an emulation of a computer system.
### Waterfall
-A [model](http://www.umsl.edu/~hugheyd/is6840/waterfall.html) of building software that involves collecting all requirements from the customer, then building and refining all the requirements and finally delivering the complete software to the customer that meets all the requirements they specified.
+A [model](http://www.umsl.edu/~hugheyd/is6840/waterfall.html) of building software that involves collecting all requirements from the customer, then building and refining all the requirements and finally delivering the complete software to the customer that meets all the requirements they specified.
### Webhooks
-A way for for an app to [provide](https://docs.gitlab.com/ce/user/project/integrations/webhooks.html) other applications with real-time information (e.g., send a message to a slack channel when a commit is pushed.) Read about setting up [custom git hooks](https://gitlab.com/help/administration/custom_hooks.md) for when webhooks are insufficient.
+A way for for an app to [provide](https://docs.gitlab.com/ce/user/project/integrations/webhooks.html) other applications with real-time information (e.g., send a message to a slack channel when a commit is pushed.) Read about setting up [custom git hooks](https://gitlab.com/help/administration/custom_hooks.md) for when webhooks are insufficient.
### Wiki
@@ -585,5 +585,5 @@ A [website/system](http://www.wiki.com/) that allows for collaborative editing o
### YAML
-A human-readable data serialization [language](http://www.yaml.org/about.html) that takes concepts from programming languages such as C, Perl, and Python, and ideas from XML and the data format of electronic mail.
+A human-readable data serialization [language](http://www.yaml.org/about.html) that takes concepts from programming languages such as C, Perl, and Python, and ideas from XML and the data format of electronic mail.
diff --git a/doc/update/7.0-to-7.1.md b/doc/update/7.0-to-7.1.md
index c717affebd3..2e9457aa142 100644
--- a/doc/update/7.0-to-7.1.md
+++ b/doc/update/7.0-to-7.1.md
@@ -136,3 +136,5 @@ 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/7-1-stable/config/gitlab.yml.example
diff --git a/doc/update/7.1-to-7.2.md b/doc/update/7.1-to-7.2.md
index d01f8528e14..e5045b5570f 100644
--- a/doc/update/7.1-to-7.2.md
+++ b/doc/update/7.1-to-7.2.md
@@ -135,3 +135,5 @@ 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/7-2-stable/config/gitlab.yml.example
diff --git a/doc/update/7.10-to-7.11.md b/doc/update/7.10-to-7.11.md
index 79bc6de1e46..89213ba7178 100644
--- a/doc/update/7.10-to-7.11.md
+++ b/doc/update/7.10-to-7.11.md
@@ -65,7 +65,7 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
#### New configuration options for `gitlab.yml`
-There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them to your current `gitlab.yml`.
+There are new configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them to your current `gitlab.yml`.
```
git diff origin/7-10-stable:config/gitlab.yml.example origin/7-11-stable:config/gitlab.yml.example
@@ -101,3 +101,5 @@ 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/7-11-stable/config/gitlab.yml.example
diff --git a/doc/update/7.11-to-7.12.md b/doc/update/7.11-to-7.12.md
index cc14a135926..3865186918c 100644
--- a/doc/update/7.11-to-7.12.md
+++ b/doc/update/7.11-to-7.12.md
@@ -91,7 +91,7 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
#### New configuration options for `gitlab.yml`
-There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them to your current `gitlab.yml`.
+There are new configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them to your current `gitlab.yml`.
```
git diff origin/7-11-stable:config/gitlab.yml.example origin/7-12-stable:config/gitlab.yml.example
@@ -127,3 +127,5 @@ 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/7-12-stable/config/gitlab.yml.example
diff --git a/doc/update/7.12-to-7.13.md b/doc/update/7.12-to-7.13.md
index 57ebe3261b6..4c8d8f1f741 100644
--- a/doc/update/7.12-to-7.13.md
+++ b/doc/update/7.12-to-7.13.md
@@ -91,7 +91,7 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
#### New configuration options for `gitlab.yml`
-There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them to your current `gitlab.yml`.
+There are new configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them to your current `gitlab.yml`.
```
git diff origin/7-12-stable:config/gitlab.yml.example origin/7-13-stable:config/gitlab.yml.example
@@ -127,3 +127,5 @@ 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/7-13-stable/config/gitlab.yml.example
diff --git a/doc/update/7.13-to-7.14.md b/doc/update/7.13-to-7.14.md
index 6dd9727fb49..934898da5a1 100644
--- a/doc/update/7.13-to-7.14.md
+++ b/doc/update/7.13-to-7.14.md
@@ -91,7 +91,7 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
#### New configuration options for `gitlab.yml`
-There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them to your current `gitlab.yml`.
+There are new configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them to your current `gitlab.yml`.
```
git diff origin/7-13-stable:config/gitlab.yml.example origin/7-14-stable:config/gitlab.yml.example
@@ -127,3 +127,5 @@ 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/7-14-stable/config/gitlab.yml.example
diff --git a/doc/update/7.14-to-8.0.md b/doc/update/7.14-to-8.0.md
index 117e2afaaa0..25fa6d93f06 100644
--- a/doc/update/7.14-to-8.0.md
+++ b/doc/update/7.14-to-8.0.md
@@ -143,7 +143,7 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
#### New configuration options for `gitlab.yml`
-There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
+There are new configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them manually to your current `gitlab.yml`:
```sh
git diff origin/7-14-stable:config/gitlab.yml.example origin/8-0-stable:config/gitlab.yml.example
@@ -227,3 +227,5 @@ this is likely due to an outdated Nginx or Apache configuration, or a missing or
misconfigured `gitlab-git-http-server` instance. Double-check that you correctly
completed [Step 5](#5-install-gitlab-git-http-server) to install the daemon and
[Step 8](#new-nginx-configuration) to reconfigure Nginx.
+
+[yaml]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-0-stable/config/gitlab.yml.example
diff --git a/doc/update/7.2-to-7.3.md b/doc/update/7.2-to-7.3.md
index 0e91e682175..d3391ddd225 100644
--- a/doc/update/7.2-to-7.3.md
+++ b/doc/update/7.2-to-7.3.md
@@ -143,3 +143,5 @@ 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/7-3-stable/config/gitlab.yml.example
diff --git a/doc/update/7.3-to-7.4.md b/doc/update/7.3-to-7.4.md
index 4df9127dd5f..6d632dc3c8e 100644
--- a/doc/update/7.3-to-7.4.md
+++ b/doc/update/7.3-to-7.4.md
@@ -75,7 +75,7 @@ sudo -u git -H editor config/unicorn.rb
#### MySQL Databases: Update database.yml config file
-* Add `collation: utf8_general_ci` to `config/database.yml` as seen in [config/database.yml.mysql](/config/database.yml.mysql)
+* Add `collation: utf8_general_ci` to `config/database.yml` as seen in [config/database.yml.mysql][mysql]:
```
sudo -u git -H editor config/database.yml
@@ -192,6 +192,5 @@ 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/7-4-stable/config/gitlab.yml.example
+[mysql]: https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/config/database.yml.mysql
diff --git a/doc/update/7.4-to-7.5.md b/doc/update/7.4-to-7.5.md
index 673eab3c56e..ec50706d421 100644
--- a/doc/update/7.4-to-7.5.md
+++ b/doc/update/7.4-to-7.5.md
@@ -73,8 +73,8 @@ git diff origin/7-4-stable:config/gitlab.yml.example origin/7-5-stable:config/gi
#### Change Nginx settings
-* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`](/lib/support/nginx/gitlab) but with your settings
-* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`](/lib/support/nginx/gitlab-ssl) but with your setting
+* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`][nginx] but with your settings
+* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`][nginx-ssl] but with your setting
### 6. Start application
@@ -106,3 +106,7 @@ 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/7-5-stable/config/gitlab.yml.example
+[nginx]: https://gitlab.com/gitlab-org/gitlab-ce/blob/7-5-stable/lib/support/nginx/gitlab
+[nginx-ssl]: https://gitlab.com/gitlab-org/gitlab-ce/blob/7-5-stable/lib/support/nginx/gitlab-ssl
diff --git a/doc/update/7.5-to-7.6.md b/doc/update/7.5-to-7.6.md
index 35cd437fdc4..331f5de080e 100644
--- a/doc/update/7.5-to-7.6.md
+++ b/doc/update/7.5-to-7.6.md
@@ -67,7 +67,7 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
#### New configuration options for `gitlab.yml`
-There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them to your current `gitlab.yml`.
+There are new configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them to your current `gitlab.yml`.
```
git diff origin/7-5-stable:config/gitlab.yml.example origin/7-6-stable:config/gitlab.yml.example
@@ -75,12 +75,12 @@ git diff origin/7-5-stable:config/gitlab.yml.example origin/7-6-stable:config/gi
#### Change Nginx settings
-* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`](/lib/support/nginx/gitlab) but with your settings
-* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`](/lib/support/nginx/gitlab-ssl) but with your setting
+* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`][nginx] but with your settings
+* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`][nginx-ssl] but with your setting
#### Setup time zone (optional)
-Consider setting the time zone in `gitlab.yml` otherwise GitLab will default to UTC. If you set a time zone previously in [`application.rb`](config/application.rb) (unlikely), unset it.
+Consider setting the time zone in `gitlab.yml` otherwise GitLab will default to UTC. If you set a time zone previously in [`application.rb`][app] (unlikely), unset it.
### 6. Start application
@@ -112,3 +112,8 @@ 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/7-6-stable/config/gitlab.yml.example
+[app]: https://gitlab.com/gitlab-org/gitlab-ce/blob/7-6-stable/config/application.rb
+[nginx]: https://gitlab.com/gitlab-org/gitlab-ce/blob/7-6-stable/lib/support/nginx/gitlab
+[nginx-ssl]: https://gitlab.com/gitlab-org/gitlab-ce/blob/7-6-stable/lib/support/nginx/gitlab-ssl
diff --git a/doc/update/7.6-to-7.7.md b/doc/update/7.6-to-7.7.md
index 910c7dcdd3c..918b10fbd95 100644
--- a/doc/update/7.6-to-7.7.md
+++ b/doc/update/7.6-to-7.7.md
@@ -67,7 +67,7 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
#### New configuration options for `gitlab.yml`
-There are new configuration options available for [`gitlab.yml`](/config/gitlab.yml.example). View them with the command below and apply them to your current `gitlab.yml`.
+There are new configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them to your current `gitlab.yml`.
```
git diff origin/7-6-stable:config/gitlab.yml.example origin/7-7-stable:config/gitlab.yml.example
@@ -75,12 +75,12 @@ git diff origin/7-6-stable:config/gitlab.yml.example origin/7-7-stable:config/gi
#### Change Nginx settings
-* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`](/lib/support/nginx/gitlab) but with your settings
-* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`](/lib/support/nginx/gitlab-ssl) but with your setting
+* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`][nginx] but with your settings
+* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`][nginx-ssl] but with your setting
#### Setup time zone (optional)
-Consider setting the time zone in `gitlab.yml` otherwise GitLab will default to UTC. If you set a time zone previously in [`application.rb`](config/application.rb) (unlikely), unset it.
+Consider setting the time zone in `gitlab.yml` otherwise GitLab will default to UTC. If you set a time zone previously in [`application.rb`][app] (unlikely), unset it.
### 6. Start application
@@ -101,7 +101,7 @@ If all items are green, then congratulations upgrade is complete!
### 8. GitHub settings (if applicable)
-If you are using GitHub as an OAuth provider for authentication, you should change the callback URL so that it
+If you are using GitHub as an OAuth provider for authentication, you should change the callback URL so that it
only contains a root URL (ex. `https://gitlab.example.com/`)
## Things went south? Revert to previous version (7.6)
@@ -117,3 +117,8 @@ 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/7-7-stable/config/gitlab.yml.example
+[app]: https://gitlab.com/gitlab-org/gitlab-ce/blob/7-7-stable/config/application.rb
+[nginx]: https://gitlab.com/gitlab-org/gitlab-ce/blob/7-7-stable/lib/support/nginx/gitlab
+[nginx-ssl]: https://gitlab.com/gitlab-org/gitlab-ce/blob/7-7-stable/lib/support/nginx/gitlab-ssl
diff --git a/doc/update/7.7-to-7.8.md b/doc/update/7.7-to-7.8.md
index 46ca163c1bb..84e0464a824 100644
--- a/doc/update/7.7-to-7.8.md
+++ b/doc/update/7.7-to-7.8.md
@@ -67,7 +67,7 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
#### New configuration options for `gitlab.yml`
-There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them to your current `gitlab.yml`.
+There are new configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them to your current `gitlab.yml`.
```
git diff origin/7-7-stable:config/gitlab.yml.example origin/7-8-stable:config/gitlab.yml.example
@@ -75,13 +75,13 @@ git diff origin/7-7-stable:config/gitlab.yml.example origin/7-8-stable:config/gi
#### Change Nginx settings
-* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`](/lib/support/nginx/gitlab) but with your settings.
-* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`](/lib/support/nginx/gitlab-ssl) but with your settings.
+* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`][nginx] but with your settings.
+* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`][nginx-ssl] but with your settings.
* A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section.
#### Setup time zone (optional)
-Consider setting the time zone in `gitlab.yml` otherwise GitLab will default to UTC. If you set a time zone previously in [`application.rb`](config/application.rb) (unlikely), unset it.
+Consider setting the time zone in `gitlab.yml` otherwise GitLab will default to UTC. If you set a time zone previously in [`application.rb`][app] (unlikely), unset it.
### 6. Start application
@@ -118,3 +118,8 @@ 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/7-8-stable/config/gitlab.yml.example
+[app]: https://gitlab.com/gitlab-org/gitlab-ce/blob/7-8-stable/config/application.rb
+[nginx]: https://gitlab.com/gitlab-org/gitlab-ce/blob/7-8-stable/lib/support/nginx/gitlab
+[nginx-ssl]: https://gitlab.com/gitlab-org/gitlab-ce/blob/7-8-stable/lib/support/nginx/gitlab-ssl
diff --git a/doc/update/7.8-to-7.9.md b/doc/update/7.8-to-7.9.md
index 6ffa21c6141..b0dc2ba1dbb 100644
--- a/doc/update/7.8-to-7.9.md
+++ b/doc/update/7.8-to-7.9.md
@@ -69,7 +69,7 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
#### New configuration options for `gitlab.yml`
-There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them to your current `gitlab.yml`.
+There are new configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them to your current `gitlab.yml`.
```
git diff origin/7-8-stable:config/gitlab.yml.example origin/7-9-stable:config/gitlab.yml.example
@@ -77,13 +77,13 @@ git diff origin/7-8-stable:config/gitlab.yml.example origin/7-9-stable:config/gi
#### Change Nginx settings
-* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`](/lib/support/nginx/gitlab) but with your settings.
-* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`](/lib/support/nginx/gitlab-ssl) but with your settings.
+* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`][nginx] but with your settings.
+* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`][nginx-ssl] but with your settings.
* A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section.
#### Setup time zone (optional)
-Consider setting the time zone in `gitlab.yml` otherwise GitLab will default to UTC. If you set a time zone previously in [`application.rb`](config/application.rb) (unlikely), unset it.
+Consider setting the time zone in `gitlab.yml` otherwise GitLab will default to UTC. If you set a time zone previously in [`application.rb`][app] (unlikely), unset it.
### 6. Start application
@@ -120,3 +120,8 @@ 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/7-9-stable/config/gitlab.yml.example
+[app]: https://gitlab.com/gitlab-org/gitlab-ce/blob/7-9-stable/config/application.rb
+[nginx]: https://gitlab.com/gitlab-org/gitlab-ce/blob/7-9-stable/lib/support/nginx/gitlab
+[nginx-ssl]: https://gitlab.com/gitlab-org/gitlab-ce/blob/7-9-stable/lib/support/nginx/gitlab-ssl
diff --git a/doc/update/7.9-to-7.10.md b/doc/update/7.9-to-7.10.md
index d1179dc2ec7..8f7f84b41ba 100644
--- a/doc/update/7.9-to-7.10.md
+++ b/doc/update/7.9-to-7.10.md
@@ -65,7 +65,7 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
#### New configuration options for `gitlab.yml`
-There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them to your current `gitlab.yml`.
+There are new configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them to your current `gitlab.yml`.
```
git diff origin/7-9-stable:config/gitlab.yml.example origin/7-10-stable:config/gitlab.yml.example
@@ -73,13 +73,13 @@ git diff origin/7-9-stable:config/gitlab.yml.example origin/7-10-stable:config/g
#### Change Nginx settings
-* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`](/lib/support/nginx/gitlab) but with your settings.
-* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`](/lib/support/nginx/gitlab-ssl) but with your settings.
+* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`][nginx] but with your settings.
+* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`][nginx-ssl] but with your settings.
* A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section.
#### Setup time zone (optional)
-Consider setting the time zone in `gitlab.yml` otherwise GitLab will default to UTC. If you set a time zone previously in [`application.rb`](config/application.rb) (unlikely), unset it.
+Consider setting the time zone in `gitlab.yml` otherwise GitLab will default to UTC. If you set a time zone previously in [`application.rb`][app] (unlikely), unset it.
### 6. Start application
@@ -116,3 +116,8 @@ 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/7-10-stable/config/gitlab.yml.example
+[app]: https://gitlab.com/gitlab-org/gitlab-ce/blob/7-10-stable/config/application.rb
+[nginx]: https://gitlab.com/gitlab-org/gitlab-ce/blob/7-10-stable/lib/support/nginx/gitlab
+[nginx-ssl]: https://gitlab.com/gitlab-org/gitlab-ce/blob/7-10-stable/lib/support/nginx/gitlab-ssl
diff --git a/doc/update/8.0-to-8.1.md b/doc/update/8.0-to-8.1.md
index bfb83cf79b1..6ee0c0656ee 100644
--- a/doc/update/8.0-to-8.1.md
+++ b/doc/update/8.0-to-8.1.md
@@ -108,7 +108,7 @@ For Ubuntu 16.04.1 LTS:
#### New configuration options for `gitlab.yml`
-There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
+There are new configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them manually to your current `gitlab.yml`:
```sh
git diff origin/8-0-stable:config/gitlab.yml.example origin/8-1-stable:config/gitlab.yml.example
@@ -173,3 +173,5 @@ If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of
### "You appear to have cloned an empty repository."
See the [7.14 to 8.0 update guide](7.14-to-8.0.md#troubleshooting).
+
+[yaml]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-1-stable/config/gitlab.yml.example
diff --git a/doc/update/8.1-to-8.2.md b/doc/update/8.1-to-8.2.md
index 7f36ce00e96..4c9ff5c5c0a 100644
--- a/doc/update/8.1-to-8.2.md
+++ b/doc/update/8.1-to-8.2.md
@@ -125,7 +125,7 @@ For Ubuntu 16.04.1 LTS:
#### New configuration options for `gitlab.yml`
-There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
+There are new configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them manually to your current `gitlab.yml`:
```sh
git diff origin/8-1-stable:config/gitlab.yml.example origin/8-2-stable:config/gitlab.yml.example
@@ -190,3 +190,5 @@ If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of
### "You appear to have cloned an empty repository."
See the [7.14 to 8.0 update guide](7.14-to-8.0.md#troubleshooting).
+
+[yaml]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-2-stable/config/gitlab.yml.example
diff --git a/doc/update/8.10-to-8.11.md b/doc/update/8.10-to-8.11.md
index 119c5f475e4..e5e3cd395df 100644
--- a/doc/update/8.10-to-8.11.md
+++ b/doc/update/8.10-to-8.11.md
@@ -114,7 +114,7 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS
#### New configuration options for `gitlab.yml`
-There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
+There are new configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them manually to your current `gitlab.yml`:
```sh
git diff origin/8-10-stable:config/gitlab.yml.example origin/8-11-stable:config/gitlab.yml.example
@@ -195,3 +195,5 @@ 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/8-11-stable/config/gitlab.yml.example
diff --git a/doc/update/8.11-to-8.12.md b/doc/update/8.11-to-8.12.md
index cddfa7e3e01..d6b3b0ffa5a 100644
--- a/doc/update/8.11-to-8.12.md
+++ b/doc/update/8.11-to-8.12.md
@@ -113,7 +113,7 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS
#### New configuration options for `gitlab.yml`
-There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
+There are new configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them manually to your current `gitlab.yml`:
```sh
git diff origin/8-11-stable:config/gitlab.yml.example origin/8-12-stable:config/gitlab.yml.example
@@ -203,3 +203,5 @@ 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/8-12-stable/config/gitlab.yml.example
diff --git a/doc/update/8.12-to-8.13.md b/doc/update/8.12-to-8.13.md
index 8c0d3f78b55..75956aeb360 100644
--- a/doc/update/8.12-to-8.13.md
+++ b/doc/update/8.12-to-8.13.md
@@ -113,7 +113,7 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS
#### New configuration options for `gitlab.yml`
-There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
+There are new configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them manually to your current `gitlab.yml`:
```sh
git diff origin/8-12-stable:config/gitlab.yml.example origin/8-13-stable:config/gitlab.yml.example
@@ -203,3 +203,5 @@ 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/8-13-stable/config/gitlab.yml.example
diff --git a/doc/update/8.13-to-8.14.md b/doc/update/8.13-to-8.14.md
index c64d3407461..327ecb7cdc2 100644
--- a/doc/update/8.13-to-8.14.md
+++ b/doc/update/8.13-to-8.14.md
@@ -113,7 +113,7 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS
#### New configuration options for `gitlab.yml`
-There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
+There are new configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them manually to your current `gitlab.yml`:
```sh
git diff origin/8-13-stable:config/gitlab.yml.example origin/8-14-stable:config/gitlab.yml.example
@@ -203,3 +203,5 @@ 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/8-14-stable/config/gitlab.yml.example
diff --git a/doc/update/8.14-to-8.15.md b/doc/update/8.14-to-8.15.md
index b1e3b116548..a68fe3bb605 100644
--- a/doc/update/8.14-to-8.15.md
+++ b/doc/update/8.14-to-8.15.md
@@ -122,7 +122,7 @@ sudo -u git -H git checkout v4.1.1
#### New configuration options for `gitlab.yml`
-There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
+There are new 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
@@ -235,3 +235,5 @@ 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/8-15-stable/config/gitlab.yml.example
diff --git a/doc/update/8.15-to-8.16.md b/doc/update/8.15-to-8.16.md
index 2695a16ac0b..9f8f0f714d4 100644
--- a/doc/update/8.15-to-8.16.md
+++ b/doc/update/8.15-to-8.16.md
@@ -124,7 +124,7 @@ sudo -u git -H git checkout v4.1.1
#### New configuration options for `gitlab.yml`
-There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
+There are new 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
@@ -237,3 +237,5 @@ 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/8-16-stable/config/gitlab.yml.example
diff --git a/doc/update/8.16-to-8.17.md b/doc/update/8.16-to-8.17.md
index 1808232c59a..954109ba18f 100644
--- a/doc/update/8.16-to-8.17.md
+++ b/doc/update/8.16-to-8.17.md
@@ -49,7 +49,19 @@ Install Bundler:
sudo gem install bundler --no-ri --no-rdoc
```
-### 4. Get latest code
+### 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/>
+
+### 5. Get latest code
```bash
cd /home/git/gitlab
@@ -76,7 +88,7 @@ cd /home/git/gitlab
sudo -u git -H git checkout 8-17-stable-ee
```
-### 5. Install libs, migrations, etc.
+### 6. Install libs, migrations, etc.
```bash
cd /home/git/gitlab
@@ -93,13 +105,16 @@ sudo -u git -H bundle clean
# Run database migrations
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+# Install/update frontend asset dependencies
+sudo -u git -H npm install --production
+
# Clean up assets and cache
-sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+sudo -u git -H bundle exec rake gitlab:assets:clean gitlab:assets:compile 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).
-### 6. Update gitlab-workhorse
+### 7. Update gitlab-workhorse
Install and compile gitlab-workhorse. This requires
[Go 1.5](https://golang.org/dl) which should already be on your system from
@@ -111,7 +126,7 @@ cd /home/git/gitlab
sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse]" RAILS_ENV=production
```
-### 7. Update gitlab-shell
+### 8. Update gitlab-shell
```bash
cd /home/git/gitlab-shell
@@ -120,11 +135,11 @@ sudo -u git -H git fetch --all --tags
sudo -u git -H git checkout v4.1.1
```
-### 8. Update configuration files
+### 9. Update configuration files
#### New configuration options for `gitlab.yml`
-There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
+There are new 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
@@ -194,14 +209,14 @@ For Ubuntu 16.04.1 LTS:
sudo systemctl daemon-reload
```
-### 9. Start application
+### 10. Start application
```bash
sudo service gitlab start
sudo service nginx restart
```
-### 10. Check application status
+### 11. Check application status
Check if GitLab and its environment are configured correctly:
@@ -237,3 +252,5 @@ 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/8-17-stable/config/gitlab.yml.example
diff --git a/doc/update/8.2-to-8.3.md b/doc/update/8.2-to-8.3.md
index dd3fdafd8d1..f28896c2227 100644
--- a/doc/update/8.2-to-8.3.md
+++ b/doc/update/8.2-to-8.3.md
@@ -114,7 +114,7 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS
#### New configuration options for `gitlab.yml`
-There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
+There are new configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them manually to your current `gitlab.yml`:
```sh
git diff origin/8-2-stable:config/gitlab.yml.example origin/8-3-stable:config/gitlab.yml.example
@@ -211,3 +211,5 @@ If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of
### "You appear to have cloned an empty repository."
See the [7.14 to 8.0 update guide](7.14-to-8.0.md#troubleshooting).
+
+[yaml]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-3-stable/config/gitlab.yml.example
diff --git a/doc/update/8.3-to-8.4.md b/doc/update/8.3-to-8.4.md
index e62d894609a..8b89455ca87 100644
--- a/doc/update/8.3-to-8.4.md
+++ b/doc/update/8.3-to-8.4.md
@@ -84,7 +84,7 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS
#### New configuration options for `gitlab.yml`
-There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
+There are new configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them manually to your current `gitlab.yml`:
```sh
git diff origin/8-3-stable:config/gitlab.yml.example origin/8-4-stable:config/gitlab.yml.example
@@ -98,7 +98,7 @@ We updated the init script for GitLab in order to set a specific PATH for gitlab
cd /home/git/gitlab
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
```
-
+
For Ubuntu 16.04.1 LTS:
sudo systemctl daemon-reload
@@ -135,3 +135,5 @@ 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/8-4-stable/config/gitlab.yml.example
diff --git a/doc/update/8.4-to-8.5.md b/doc/update/8.4-to-8.5.md
index 678cc69d773..0eedfaee2db 100644
--- a/doc/update/8.4-to-8.5.md
+++ b/doc/update/8.4-to-8.5.md
@@ -88,7 +88,7 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS
#### New configuration options for `gitlab.yml`
-There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
+There are new configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them manually to your current `gitlab.yml`:
```sh
git diff origin/8-4-stable:config/gitlab.yml.example origin/8-5-stable:config/gitlab.yml.example
@@ -119,7 +119,7 @@ via [/etc/default/gitlab].
Ensure you're still up-to-date with the latest init script changes:
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
-
+
For Ubuntu 16.04.1 LTS:
sudo systemctl daemon-reload
@@ -156,3 +156,5 @@ 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/8-5-stable/config/gitlab.yml.example
diff --git a/doc/update/8.5-to-8.6.md b/doc/update/8.5-to-8.6.md
index a76346516b9..851056161bb 100644
--- a/doc/update/8.5-to-8.6.md
+++ b/doc/update/8.5-to-8.6.md
@@ -107,7 +107,7 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS
#### New configuration options for `gitlab.yml`
-There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
+There are new configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them manually to your current `gitlab.yml`:
```sh
git diff origin/8-5-stable:config/gitlab.yml.example origin/8-6-stable:config/gitlab.yml.example
@@ -175,3 +175,5 @@ 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/8-6-stable/config/gitlab.yml.example
diff --git a/doc/update/8.6-to-8.7.md b/doc/update/8.6-to-8.7.md
index 05ef4e61759..34c727260aa 100644
--- a/doc/update/8.6-to-8.7.md
+++ b/doc/update/8.6-to-8.7.md
@@ -88,7 +88,7 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS
#### New configuration options for `gitlab.yml`
-There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
+There are new configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them manually to your current `gitlab.yml`:
```sh
git diff origin/8-6-stable:config/gitlab.yml.example origin/8-7-stable:config/gitlab.yml.example
@@ -164,3 +164,5 @@ 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/8-7-stable/config/gitlab.yml.example
diff --git a/doc/update/8.7-to-8.8.md b/doc/update/8.7-to-8.8.md
index 8ce434e5f78..6feeb1919de 100644
--- a/doc/update/8.7-to-8.8.md
+++ b/doc/update/8.7-to-8.8.md
@@ -88,7 +88,7 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS
#### New configuration options for `gitlab.yml`
-There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
+There are new configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them manually to your current `gitlab.yml`:
```sh
git diff origin/8-7-stable:config/gitlab.yml.example origin/8-8-stable:config/gitlab.yml.example
@@ -164,3 +164,5 @@ 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/8-8-stable/config/gitlab.yml.example
diff --git a/doc/update/8.8-to-8.9.md b/doc/update/8.8-to-8.9.md
index aa077316bbe..61cdf8854d4 100644
--- a/doc/update/8.8-to-8.9.md
+++ b/doc/update/8.8-to-8.9.md
@@ -104,7 +104,7 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS
#### New configuration options for `gitlab.yml`
-There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
+There are new configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them manually to your current `gitlab.yml`:
```sh
git diff origin/8-8-stable:config/gitlab.yml.example origin/8-9-stable:config/gitlab.yml.example
@@ -193,3 +193,5 @@ 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/8-9-stable/config/gitlab.yml.example
diff --git a/doc/update/8.9-to-8.10.md b/doc/update/8.9-to-8.10.md
index bb2c79fbb84..d6b2f11d49a 100644
--- a/doc/update/8.9-to-8.10.md
+++ b/doc/update/8.9-to-8.10.md
@@ -104,7 +104,7 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS
#### New configuration options for `gitlab.yml`
-There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
+There are new configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them manually to your current `gitlab.yml`:
```sh
git diff origin/8-9-stable:config/gitlab.yml.example origin/8-10-stable:config/gitlab.yml.example
@@ -193,3 +193,5 @@ 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/8-10-stable/config/gitlab.yml.example
diff --git a/doc/user/account/security.md b/doc/user/account/security.md
index 2459f913583..f4078876fab 100644
--- a/doc/user/account/security.md
+++ b/doc/user/account/security.md
@@ -1 +1 @@
-This document was moved to [profile](../profile/index.md).
+This document was moved to [profile](../profile/account/index.md).
diff --git a/doc/user/admin_area/settings/continuous_integration.md b/doc/user/admin_area/settings/continuous_integration.md
index 34e2e557f89..eb6f915f3f4 100644
--- a/doc/user/admin_area/settings/continuous_integration.md
+++ b/doc/user/admin_area/settings/continuous_integration.md
@@ -2,19 +2,37 @@
## Maximum artifacts size
-The maximum size of the [build artifacts][art-yml] can be set in the Admin area
-of your GitLab instance. The value is in MB and the default is 100MB. Note that
-this setting is set for each build.
+The maximum size of the [job artifacts][art-yml] can be set in the Admin area
+of your GitLab instance. The value is in *MB* and the default is 100MB. Note
+that this setting is set for each job.
1. Go to **Admin area > Settings** (`/admin/application_settings`).
![Admin area settings button](img/admin_area_settings_button.png)
-1. Change the value of the maximum artifacts size (in MB):
+1. Change the value of maximum artifacts size (in MB):
![Admin area maximum artifacts size](img/admin_area_maximum_artifacts_size.png)
1. Hit **Save** for the changes to take effect.
+## Default artifacts expiration
-[art-yml]: ../../../administration/build_artifacts.md
+The default expiration time of the [job artifacts][art-yml] can be set in
+the Admin area of your GitLab instance. The syntax of duration is described
+in [artifacts:expire_in][duration-syntax]. The default is `30 days`. Note that
+this setting is set for each job. Set it to 0 if you don't want default
+expiration.
+
+1. Go to **Admin area > Settings** (`/admin/application_settings`).
+
+ ![Admin area settings button](img/admin_area_settings_button.png)
+
+1. Change the value of default expiration time ([syntax][duration-syntax]):
+
+ ![Admin area default artifacts expiration](img/admin_area_default_artifacts_expiration.png)
+
+1. Hit **Save** for the changes to take effect.
+
+[art-yml]: ../../../administration/job_artifacts.md
+[duration-syntax]: ../../../ci/yaml/README.md#artifactsexpire_in
diff --git a/doc/user/admin_area/settings/img/admin_area_default_artifacts_expiration.png b/doc/user/admin_area/settings/img/admin_area_default_artifacts_expiration.png
new file mode 100644
index 00000000000..50a86ede56b
--- /dev/null
+++ b/doc/user/admin_area/settings/img/admin_area_default_artifacts_expiration.png
Binary files differ
diff --git a/doc/user/admin_area/settings/img/admin_area_maximum_artifacts_size.png b/doc/user/admin_area/settings/img/admin_area_maximum_artifacts_size.png
index b7d6671902a..33fd29e2039 100644
--- a/doc/user/admin_area/settings/img/admin_area_maximum_artifacts_size.png
+++ b/doc/user/admin_area/settings/img/admin_area_maximum_artifacts_size.png
Binary files differ
diff --git a/doc/user/admin_area/settings/sign_up_restrictions.md b/doc/user/admin_area/settings/sign_up_restrictions.md
index 4b540473a6e..603b826e7f2 100644
--- a/doc/user/admin_area/settings/sign_up_restrictions.md
+++ b/doc/user/admin_area/settings/sign_up_restrictions.md
@@ -1,5 +1,20 @@
# Sign-up restrictions
+You can block email addresses of specific domains, or whitelist only some
+specifc domains via the **Application Settings** in the Admin area.
+
+>**Note**: These restrictions are only applied during sign-up. An admin is
+able to add add a user through the admin panel with a disallowed domain. Also
+note that the users can change their email addresses after signup to
+disallowed domains.
+
+## Whitelist email domains
+
+> [Introduced][ce-598] in GitLab 7.11.0
+
+You can restrict users to only signup using email addresses matching the given
+domains list.
+
## Blacklist email domains
> [Introduced][ce-5259] in GitLab 8.10.
@@ -9,13 +24,16 @@ from creating an account on your GitLab server. This is particularly useful to
prevent spam. Disposable email addresses are usually used by malicious users to
create dummy accounts and spam issues.
+## Settings
+
This feature can be activated via the **Application Settings** in the Admin area,
and you have the option of entering the list manually, or uploading a file with
the list.
-The blacklist accepts wildcards, so you can use `*.test.com` to block every
-`test.com` subdomain, or `*.io` to block all domains ending in `.io`. Domains
-should be separated by a whitespace, semicolon, comma, or a new line.
+Both whitelist and blacklist accept wildcards, so for example, you can use
+`*.company.com` to accept every `company.com` subdomain, or `*.io` to block all
+domains ending in `.io`. Domains should be separated by a whitespace,
+semicolon, comma, or a new line.
![Domain Blacklist](img/domain_blacklist.png)
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index 008872b59a7..c14db17b0e6 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -237,23 +237,24 @@ GFM will turn that reference into a link so you can navigate between them easily
GFM will recognize the following:
-| input | references |
-|:-----------------------|:--------------------------- |
-| `@user_name` | specific user |
-| `@group_name` | specific group |
-| `@all` | entire team |
-| `#123` | issue |
-| `!123` | merge request |
-| `$123` | snippet |
-| `~123` | label by ID |
-| `~bug` | one-word label by name |
-| `~"feature request"` | multi-word label by name |
-| `%123` | milestone by ID |
-| `%v1.23` | one-word milestone by name |
-| `%"release candidate"` | multi-word milestone by name |
-| `9ba12248` | specific commit |
-| `9ba12248...b19a04f5` | commit range comparison |
-| `[README](doc/README)` | repository file references |
+| input | references |
+|:---------------------------|:--------------------------------|
+| `@user_name` | specific user |
+| `@group_name` | specific group |
+| `@all` | entire team |
+| `#123` | issue |
+| `!123` | merge request |
+| `$123` | snippet |
+| `~123` | label by ID |
+| `~bug` | one-word label by name |
+| `~"feature request"` | multi-word label by name |
+| `%123` | milestone by ID |
+| `%v1.23` | one-word milestone by name |
+| `%"release candidate"` | multi-word milestone by name |
+| `9ba12248` | specific commit |
+| `9ba12248...b19a04f5` | commit range comparison |
+| `[README](doc/README)` | repository file references |
+| `[README](doc/README#L13)` | repository file line references |
GFM also recognizes certain cross-project references:
@@ -523,35 +524,12 @@ There are two ways to create links, inline-style and reference-style.
[1]: http://slashdot.org
[link text itself]: https://www.reddit.com
-[I'm an inline-style link](https://www.google.com)
-
-[I'm a reference-style link][Arbitrary case-insensitive reference text]
-
-[I'm a relative reference to a repository file](LICENSE)[^1]
-
-[I am an absolute reference within the repository](/doc/user/markdown.md)
-
-[I link to the Milestones page](/../milestones)
-
-[You can use numbers for reference-style link definitions][1]
-
-Or leave it empty and use the [link text itself][]
-
-Some text to show that the reference links can follow later.
-
-[arbitrary case-insensitive reference text]: https://www.mozilla.org
-[1]: http://slashdot.org
-[link text itself]: https://www.reddit.com
-
-**Note**
-
-Relative links do not allow referencing project files in a wiki page or wiki page in a project file. The reason for this is that, in GitLab, wiki is always a separate git repository. For example:
-
-`[I'm a reference-style link](style)`
-
+>**Note:**
+Relative links do not allow referencing project files in a wiki page or wiki
+page in a project file. The reason for this is that, in GitLab, wiki is always
+a separate Git repository. For example, `[I'm a reference-style link](style)`
will point the link to `wikis/style` when the link is inside of a wiki markdown file.
-
### Images
Here's our logo (hover to see the title text):
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index e87cae092a5..b49a244160a 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -22,9 +22,9 @@ The following table depicts the various user permission levels in a project.
| Create confidential issue | ✓ | ✓ | ✓ | ✓ | ✓ |
| View confidential issues | (✓) [^1] | ✓ | ✓ | ✓ | ✓ |
| Leave comments | ✓ | ✓ | ✓ | ✓ | ✓ |
-| See a list of builds | ✓ [^2] | ✓ | ✓ | ✓ | ✓ |
-| See a build log | ✓ [^2] | ✓ | ✓ | ✓ | ✓ |
-| Download and browse build artifacts | ✓ [^2] | ✓ | ✓ | ✓ | ✓ |
+| See a list of jobs | ✓ [^2] | ✓ | ✓ | ✓ | ✓ |
+| See a job log | ✓ [^2] | ✓ | ✓ | ✓ | ✓ |
+| Download and browse job artifacts | ✓ [^2] | ✓ | ✓ | ✓ | ✓ |
| View wiki pages | ✓ | ✓ | ✓ | ✓ | ✓ |
| Pull project code | | ✓ | ✓ | ✓ | ✓ |
| Download project | | ✓ | ✓ | ✓ | ✓ |
@@ -46,7 +46,7 @@ The following table depicts the various user permission levels in a project.
| Remove non-protected branches | | | ✓ | ✓ | ✓ |
| Add tags | | | ✓ | ✓ | ✓ |
| Write a wiki | | | ✓ | ✓ | ✓ |
-| Cancel and retry builds | | | ✓ | ✓ | ✓ |
+| Cancel and retry jobs | | | ✓ | ✓ | ✓ |
| Create or update commit status | | | ✓ | ✓ | ✓ |
| Update a container registry | | | ✓ | ✓ | ✓ |
| Remove a container registry image | | | ✓ | ✓ | ✓ |
@@ -60,7 +60,7 @@ The following table depicts the various user permission levels in a project.
| Add deploy keys to project | | | | ✓ | ✓ |
| Configure project hooks | | | | ✓ | ✓ |
| Manage runners | | | | ✓ | ✓ |
-| Manage build triggers | | | | ✓ | ✓ |
+| Manage job triggers | | | | ✓ | ✓ |
| Manage variables | | | | ✓ | ✓ |
| Manage pages | | | | ✓ | ✓ |
| Manage pages domains and certificates | | | | ✓ | ✓ |
@@ -134,8 +134,8 @@ instance and project. In addition, all admins can use the admin interface under
| Action | Guest, Reporter | Developer | Master | Admin |
|---------------------------------------|-----------------|-------------|----------|--------|
-| See commits and builds | ✓ | ✓ | ✓ | ✓ |
-| Retry or cancel build | | ✓ | ✓ | ✓ |
+| See commits and jobs | ✓ | ✓ | ✓ | ✓ |
+| Retry or cancel job | | ✓ | ✓ | ✓ |
| Remove project | | | ✓ | ✓ |
| Create project | | | ✓ | ✓ |
| Change project configuration | | | ✓ | ✓ |
@@ -144,18 +144,18 @@ instance and project. In addition, all admins can use the admin interface under
| See events in the system | | | | ✓ |
| Admin interface | | | | ✓ |
-### Builds permissions
+### Jobs permissions
>**Note:**
-GitLab 8.12 has a completely redesigned build permissions system.
+GitLab 8.12 has a completely redesigned job permissions system.
Read all about the [new model and its implications][new-mod].
-This table shows granted privileges for builds triggered by specific types of
+This table shows granted privileges for jobs triggered by specific types of
users:
| Action | Guest, Reporter | Developer | Master | Admin |
|---------------------------------------------|-----------------|-------------|----------|--------|
-| Run CI build | | ✓ | ✓ | ✓ |
+| Run CI job | | ✓ | ✓ | ✓ |
| Clone source and LFS from current project | | ✓ | ✓ | ✓ |
| Clone source and LFS from public projects | | ✓ | ✓ | ✓ |
| Clone source and LFS from internal projects | | ✓ [^4] | ✓ [^4] | ✓ |
diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md
index a23ad79ae1d..eaa39a0c4ea 100644
--- a/doc/user/profile/account/two_factor_authentication.md
+++ b/doc/user/profile/account/two_factor_authentication.md
@@ -213,5 +213,5 @@ your GitLab server's time is synchronized via a service like NTP. Otherwise,
you may have cases where authorization always fails because of time differences.
[Google Authenticator]: https://support.google.com/accounts/answer/1066447?hl=en
-[FreeOTP]: https://fedorahosted.org/freeotp/
+[FreeOTP]: https://freeotp.github.io/
[YubiKey]: https://www.yubico.com/products/yubikey-hardware/
diff --git a/doc/user/project/builds/artifacts.md b/doc/user/project/builds/artifacts.md
index 88f1863dddb..514c729b37d 100644
--- a/doc/user/project/builds/artifacts.md
+++ b/doc/user/project/builds/artifacts.md
@@ -1,136 +1 @@
-# Introduction to build artifacts
-
->**Notes:**
->- Since GitLab 8.2 and GitLab Runner 0.7.0, build artifacts that are created by
- GitLab Runner are uploaded to GitLab and are downloadable as a single archive
- (`tar.gz`) using the GitLab UI.
->- Starting from GitLab 8.4 and GitLab Runner 1.0, the artifacts archive format
- changed to `ZIP`, and it is now possible to browse its contents, with the added
- ability of downloading the files separately.
->- The artifacts browser will be available only for new artifacts that are sent
- to GitLab using GitLab Runner version 1.0 and up. It will not be possible to
- browse old artifacts already uploaded to GitLab.
->- This is the user documentation. For the administration guide see
- [administration/build_artifacts.md](../../../administration/build_artifacts.md).
-
-Artifacts is a list of files and directories which are attached to a build
-after it completes successfully. This feature is enabled by default in all GitLab installations.
-
-## Defining artifacts in `.gitlab-ci.yml`
-
-A simple example of using the artifacts definition in `.gitlab-ci.yml` would be
-the following:
-
-```yaml
-pdf:
- script: xelatex mycv.tex
- artifacts:
- paths:
- - mycv.pdf
-```
-
-A job named `pdf` calls the `xelatex` command in order to build a pdf file from
-the latex source file `mycv.tex`. We then define the `artifacts` paths which in
-turn are defined with the `paths` keyword. All paths to files and directories
-are relative to the repository that was cloned during the build.
-
-For more examples on artifacts, follow the artifacts reference in
-[`.gitlab-ci.yml` documentation](../../../ci/yaml/README.md#artifacts).
-
-## Browsing build artifacts
-
-When GitLab receives an artifacts archive, an archive metadata file is also
-generated. This metadata file describes all the entries that are located in the
-artifacts archive itself. The metadata file is in a binary format, with
-additional GZIP compression.
-
-GitLab does not extract the artifacts archive in order to save space, memory
-and disk I/O. It instead inspects the metadata file which contains all the
-relevant information. This is especially important when there is a lot of
-artifacts, or an archive is a very large file.
-
----
-
-After a build finishes, if you visit the build's specific page, you can see
-that there are two buttons. One is for downloading the artifacts archive and
-the other for browsing its contents.
-
-![Build artifacts browser button](img/build_artifacts_browser_button.png)
-
----
-
-The archive browser shows the name and the actual file size of each file in the
-archive. If your artifacts contained directories, then you are also able to
-browse inside them.
-
-Below you can see how browsing looks like. In this case we have browsed inside
-the archive and at this point there is one directory and one HTML file.
-
-![Build artifacts browser](img/build_artifacts_browser.png)
-
----
-
-## Downloading build artifacts
-
->**Note:**
-GitLab does not extract the entire artifacts archive to send just a single file
-to the user. When clicking on a specific file, [GitLab Workhorse] extracts it
-from the archive and the download begins. This implementation saves space,
-memory and disk I/O.
-
-If you need to download the whole archive, there are buttons in various places
-inside GitLab that make that possible.
-
-1. While on the pipelines page, you can see the download icon for each build's
- artifacts archive in the right corner:
-
- ![Build artifacts in Pipelines page](img/build_artifacts_pipelines_page.png)
-
-1. While on the builds page, you can see the download icon for each build's
- artifacts archive in the right corner:
-
- ![Build artifacts in Builds page](img/build_artifacts_builds_page.png)
-
-1. While inside a specific build, you are presented with a download button
- along with the one that browses the archive:
-
- ![Build artifacts browser button](img/build_artifacts_browser_button.png)
-
-1. And finally, when browsing an archive you can see the download button at
- the top right corner:
-
- ![Build artifacts browser](img/build_artifacts_browser.png)
-
-## Downloading the latest build artifacts
-
-It is possible to download the latest artifacts of a build via a well known URL
-so you can use it for scripting purposes.
-
-The structure of the URL is the following:
-
-```
-https://example.com/<namespace>/<project>/builds/artifacts/<ref>/download?job=<job_name>
-```
-
-For example, to download the latest artifacts of the job named `rspec 6 20` of
-the `master` branch of the `gitlab-ce` project that belongs to the `gitlab-org`
-namespace, the URL would be:
-
-```
-https://gitlab.com/gitlab-org/gitlab-ce/builds/artifacts/master/download?job=rspec+6+20
-```
-
-The latest builds are also exposed in the UI in various places. Specifically,
-look for the download button in:
-
-- the main project's page
-- the branches page
-- the tags page
-
-If the latest build has failed to upload the artifacts, you can see that
-information in the UI.
-
-![Latest artifacts button](img/build_latest_artifacts_browser.png)
-
-
-[gitlab workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse "GitLab Workhorse repository"
+This document was moved to [pipelines/job_artifacts](../pipelines/job_artifacts.md).
diff --git a/doc/user/project/builds/img/build_artifacts_browser.png b/doc/user/project/builds/img/build_artifacts_browser.png
deleted file mode 100644
index 686273948d6..00000000000
--- a/doc/user/project/builds/img/build_artifacts_browser.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/builds/img/build_artifacts_browser_button.png b/doc/user/project/builds/img/build_artifacts_browser_button.png
deleted file mode 100644
index 33ef7de0415..00000000000
--- a/doc/user/project/builds/img/build_artifacts_browser_button.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/builds/img/build_artifacts_builds_page.png b/doc/user/project/builds/img/build_artifacts_builds_page.png
deleted file mode 100644
index 8f75602d592..00000000000
--- a/doc/user/project/builds/img/build_artifacts_builds_page.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/builds/img/build_artifacts_pipelines_page.png b/doc/user/project/builds/img/build_artifacts_pipelines_page.png
deleted file mode 100644
index 4bbd00ddaa0..00000000000
--- a/doc/user/project/builds/img/build_artifacts_pipelines_page.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/issue_board.png b/doc/user/project/img/issue_board.png
index 95e8532e908..b636cb294b8 100644
--- a/doc/user/project/img/issue_board.png
+++ b/doc/user/project/img/issue_board.png
Binary files differ
diff --git a/doc/user/project/img/issue_board_search_backlog.png b/doc/user/project/img/issue_board_search_backlog.png
deleted file mode 100644
index fbb67b9c18f..00000000000
--- a/doc/user/project/img/issue_board_search_backlog.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/issue_board_welcome_message.png b/doc/user/project/img/issue_board_welcome_message.png
index 5bfdac88dde..5318e6ea4a9 100644
--- a/doc/user/project/img/issue_board_welcome_message.png
+++ b/doc/user/project/img/issue_board_welcome_message.png
Binary files differ
diff --git a/doc/user/project/img/issue_boards_add_issues_modal.png b/doc/user/project/img/issue_boards_add_issues_modal.png
new file mode 100644
index 00000000000..33049dce74f
--- /dev/null
+++ b/doc/user/project/img/issue_boards_add_issues_modal.png
Binary files differ
diff --git a/doc/user/project/img/issue_boards_remove_issue.png b/doc/user/project/img/issue_boards_remove_issue.png
new file mode 100644
index 00000000000..8b3beca97cf
--- /dev/null
+++ b/doc/user/project/img/issue_boards_remove_issue.png
Binary files differ
diff --git a/doc/user/project/img/protected_branches_devs_can_push.png b/doc/user/project/img/protected_branches_devs_can_push.png
index 1c05cb8fd36..320e6eb7fee 100644
--- a/doc/user/project/img/protected_branches_devs_can_push.png
+++ b/doc/user/project/img/protected_branches_devs_can_push.png
Binary files differ
diff --git a/doc/user/project/integrations/img/services_templates_redmine_example.png b/doc/user/project/integrations/img/services_templates_redmine_example.png
index 50d20510daf..379cef9888d 100644
--- a/doc/user/project/integrations/img/services_templates_redmine_example.png
+++ b/doc/user/project/integrations/img/services_templates_redmine_example.png
Binary files differ
diff --git a/doc/user/project/integrations/services_templates.md b/doc/user/project/integrations/services_templates.md
index be6d13b6d2b..5b04d7d88b8 100644
--- a/doc/user/project/integrations/services_templates.md
+++ b/doc/user/project/integrations/services_templates.md
@@ -1,25 +1,26 @@
-# Services Templates
+# Services templates
A GitLab administrator can add a service template that sets a default for each
-project. This makes it much easier to configure individual projects.
+project. After a service template is enabled, it will be applied to new
+projects only and its details will be pre-filled on the project's Service page.
-After the template is created, the template details will be pre-filled on a
-project's Service page.
-
-## Enable a Service template
+## Enable a service template
In GitLab's Admin area, navigate to **Service Templates** and choose the
service template you wish to create.
-For example, in the image below you can see Redmine.
+## Services for external issue trackers
+
+In the image below you can see how a service template for Redmine would look
+like.
![Redmine service template](img/services_templates_redmine_example.png)
---
-**NOTE:** For each project, you will still need to configure the issue tracking
+For each project, you will still need to configure the issue tracking
URLs by replacing `:issues_tracker_id` in the above screenshot with the ID used
by your external issue tracker. Prior to GitLab v7.8, this ID was configured in
the project settings, and GitLab would automatically update the URL configured
in `gitlab.yml`. This behavior is now deprecated and all issue tracker URLs
-must be configured directly within the project's **Services** settings.
+must be configured directly within the project's **Integrations** settings.
diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md
index 9df0c765f84..ed1e867f5fb 100644
--- a/doc/user/project/integrations/webhooks.md
+++ b/doc/user/project/integrations/webhooks.md
@@ -11,7 +11,7 @@ a new issue is created. You can configure webhooks to listen for specific events
like pushes, issues or merge requests. GitLab will send a POST request with data
to the webhook URL.
-Webhooks can be used to update an external issue tracker, trigger CI builds,
+Webhooks can be used to update an external issue tracker, trigger CI jobs,
update a backup mirror, or even deploy to your production server.
Navigate to the webhooks page by going to the **Integrations** page from your
@@ -714,7 +714,7 @@ X-Gitlab-Event: Merge Request Hook
### Wiki Page events
-Triggered when a wiki page is created or edited.
+Triggered when a wiki page is created, edited or deleted.
**Request Header**:
diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md
index d1ae57c00d7..3199d370a58 100644
--- a/doc/user/project/issue_board.md
+++ b/doc/user/project/issue_board.md
@@ -1,6 +1,8 @@
# Issue board
-> [Introduced][ce-5554] in GitLab 8.11.
+>**Notes:**
+- [Introduced][ce-5554] in GitLab 8.11.
+- The Backlog column was replaced by the **Add issues** button in GitLab 8.17.
The GitLab Issue Board is a software project management tool used to plan,
organize, and visualize a workflow for a feature or product release.
@@ -28,13 +30,11 @@ Below is a table of the definitions used for GitLab's Issue Board.
| **List** | Each label that exists in the issue tracker can have its own dedicated list. Every list is named after the label it is based on and is represented by a column which contains all the issues associated with that label. You can think of a list like the results you get when you filter the issues by a label in your issue tracker. |
| **Card** | Every card represents an issue and it is shown under the list for which it has a label. The information you can see on a card consists of the issue number, the issue title, the assignee and the labels associated with it. You can drag cards around from one list to another. Issues inside lists are [ordered by priority](labels.md#prioritize-labels). |
-There are three types of lists, the ones you create based on your labels, and
-two default:
+There are two types of lists, the ones you create based on your labels, and
+one default:
-- **Backlog** (default): shows all issues that do not fall in one of the other lists. Always appears on the very left.
-- **Done** (default): shows all closed issues. Always appears on the very right.
-Label list: a list based on a label. It shows all issues with that label.
- Label list: a list based on a label. It shows all opened issues with that label.
+- **Done** (default): shows all closed issues. Always appears on the very right.
![GitLab Issue Board](img/issue_board.png)
@@ -55,10 +55,10 @@ In short, here's a list of actions you can take in an Issue Board:
If you are not able to perform one or more of the things above, make sure you
have the right [permissions](#permissions).
-## First time using the Issue Board
+## First time using the issue board
-The first time you navigate to your Issue Board, you will be presented with the
-two default lists (**Backlog** and **Done**) and a welcoming message that gives
+The first time you navigate to your Issue Board, you will be presented with
+a default list (**Done**) and a welcoming message that gives
you two options. You can either create a predefined set of labels and create
their corresponding lists to the Issue Board or opt-out and use your own lists.
@@ -93,23 +93,26 @@ in the list's heading. A confirmation dialog will appear for you to confirm.
Deleting a list doesn't have any effect in issues and labels, it's just the
list view that is removed. You can always add it back later if you need.
-## Searching issues in the Backlog list
+## Adding issues to a list
+
+You can add issues to a list by clicking the **Add issues** button that is
+present in the upper right corner of the issue board. This will open up a modal
+window where you can see all the issues that do not belong to any list.
+
+Select one or more issues by clicking on the cards and then click **Add issues**
+to add them to the selected list. You can limit the issues you want to add to
+the list by filtering by author, assignee, milestone and label.
-The very first time you start using the Issue Board, it is very likely your
-issue tracker is already populated with labels and issues. In that case,
-**Backlog** will have all the issues that don't belong to another list, and
-**Done** will have all the closed ones.
+![Bulk adding issues to lists](img/issue_boards_add_issues_modal.png)
-For performance and visibility reasons, each list shows the first 20 issues
-by default. If you have more than 20, you have to start scrolling down for the
-next 20 issues to appear. This can be cumbersome if your issue tracker hosts
-hundreds of issues, and for that reason it is easier to search for issues to
-move from **Backlog** to another list.
+## Removing an issue from a list
-Start typing in the search bar under the **Backlog** list and the relevant
-issues will appear.
+Removing an issue from a list can be done by clicking on the issue card and then
+clicking the **Remove from board** button in the sidebar. Under the hood, the
+respective label is removed, and as such it's also removed from the list and the
+board itself.
-![Issue Board search Backlog](img/issue_board_search_backlog.png)
+![Remove issue from list](img/issue_boards_remove_issue.png)
## Filtering issues
@@ -142,8 +145,8 @@ A typical workflow of using the Issue Board would be:
and gets automatically closed.
For instance you can create a list based on the label of 'Frontend' and one for
-'Backend'. A designer can start working on an issue by dragging it from
-**Backlog** to 'Frontend'. That way, everyone knows that this issue is now being
+'Backend'. A designer can start working on an issue by adding it to the
+'Frontend' list. That way, everyone knows that this issue is now being
worked on by the designers. Then, once they're done, all they have to do is
drag it over to the next list, 'Backend', where a backend developer can
eventually pick it up. Once they’re done, they move it to **Done**, to close the
diff --git a/doc/user/project/merge_requests.md b/doc/user/project/merge_requests.md
index be09337319f..84a79f04094 100644
--- a/doc/user/project/merge_requests.md
+++ b/doc/user/project/merge_requests.md
@@ -1,169 +1 @@
-# Merge Requests
-
-Merge requests allow you to exchange changes you made to source code and
-collaborate with other people on the same project.
-
-## Authorization for merge requests
-
-There are two main ways to have a merge request flow with GitLab:
-
-1. Working with [protected branches][] in a single repository
-1. Working with forks of an authoritative project
-
-[Learn more about the authorization for merge requests.](merge_requests/authorization_for_merge_requests.md)
-
-## Cherry-pick changes
-
-Cherry-pick any commit in the UI by simply clicking the **Cherry-pick** button
-in a merged merge requests or a commit.
-
-[Learn more about cherry-picking changes.](merge_requests/cherry_pick_changes.md)
-
-## Merge when pipeline succeeds
-
-When reviewing a merge request that looks ready to merge but still has one or
-more CI builds running, you can set it to be merged automatically when CI
-pipeline succeeds. This way, you don't have to wait for the pipeline to finish
-and remember to merge the request manually.
-
-[Learn more about merging when pipeline succeeds.](merge_requests/merge_when_pipeline_succeeds.md)
-
-## Resolve discussion comments in merge requests reviews
-
-Keep track of the progress during a code review with resolving comments.
-Resolving comments prevents you from forgetting to address feedback and lets
-you hide discussions that are no longer relevant.
-
-[Read more about resolving discussion comments in merge requests reviews.](merge_requests/merge_request_discussion_resolution.md)
-
-## Resolve conflicts
-
-When a merge request has conflicts, GitLab may provide the option to resolve
-those conflicts in the GitLab UI.
-
-[Learn more about resolving merge conflicts in the UI.](merge_requests/resolve_conflicts.md)
-
-## Revert changes
-
-GitLab implements Git's powerful feature to revert any commit with introducing
-a **Revert** button in merge requests and commit details.
-
-[Learn more about reverting changes in the UI](merge_requests/revert_changes.md)
-
-## Merge requests versions
-
-Every time you push to a branch that is tied to a merge request, a new version
-of merge request diff is created. When you visit a merge request that contains
-more than one pushes, you can select and compare the versions of those merge
-request diffs.
-
-[Read more about the merge requests versions.](merge_requests/versions.md)
-
-## Work In Progress merge requests
-
-To prevent merge requests from accidentally being accepted before they're
-completely ready, GitLab blocks the "Accept" button for merge requests that
-have been marked as a **Work In Progress**.
-
-[Learn more about settings a merge request as "Work In Progress".](merge_requests/work_in_progress_merge_requests.md)
-
-## Ignore whitespace changes in Merge Request diff view
-
-If you click the **Hide whitespace changes** button, you can see the diff
-without whitespace changes (if there are any). This is also working when on a
-specific commit page.
-
-![MR diff](merge_requests/img/merge_request_diff.png)
-
->**Tip:**
-You can append `?w=1` while on the diffs page of a merge request to ignore any
-whitespace changes.
-
-## Tips
-
-Here are some tips that will help you be more efficient with merge requests in
-the command line.
-
-> **Note:**
-This section might move in its own document in the future.
-
-### Checkout merge requests locally
-
-A merge request contains all the history from a repository, plus the additional
-commits added to the branch associated with the merge request. Here's a few
-tricks to checkout a merge request locally.
-
-Please note that you can checkout a merge request locally even if the source
-project is a fork (even a private fork) of the target project.
-
-#### Checkout locally by adding a git alias
-
-Add the following alias to your `~/.gitconfig`:
-
-```
-[alias]
- mr = !sh -c 'git fetch $1 merge-requests/$2/head:mr-$1-$2 && git checkout mr-$1-$2' -
-```
-
-Now you can check out a particular merge request from any repository and any
-remote. For example, to check out the merge request with ID 5 as shown in GitLab
-from the `upstream` remote, do:
-
-```
-git mr upstream 5
-```
-
-This will fetch the merge request into a local `mr-upstream-5` branch and check
-it out.
-
-#### Checkout locally by modifying `.git/config` for a given repository
-
-Locate the section for your GitLab remote in the `.git/config` file. It looks
-like this:
-
-```
-[remote "origin"]
- url = https://gitlab.com/gitlab-org/gitlab-ce.git
- fetch = +refs/heads/*:refs/remotes/origin/*
-```
-
-You can open the file with:
-
-```
-git config -e
-```
-
-Now add the following line to the above section:
-
-```
-fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/*
-```
-
-In the end, it should look like this:
-
-```
-[remote "origin"]
- url = https://gitlab.com/gitlab-org/gitlab-ce.git
- fetch = +refs/heads/*:refs/remotes/origin/*
- fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/*
-```
-
-Now you can fetch all the merge requests:
-
-```
-git fetch origin
-
-...
-From https://gitlab.com/gitlab-org/gitlab-ce.git
- * [new ref] refs/merge-requests/1/head -> origin/merge-requests/1
- * [new ref] refs/merge-requests/2/head -> origin/merge-requests/2
-...
-```
-
-And to check out a particular merge request:
-
-```
-git checkout origin/merge-requests/1
-```
-
-[protected branches]: protected_branches.md
+This document was moved to [merge_requests/index.md](merge_requests/index.md).
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
new file mode 100644
index 00000000000..c759b7aaa4a
--- /dev/null
+++ b/doc/user/project/merge_requests/index.md
@@ -0,0 +1,169 @@
+# Merge requests
+
+Merge requests allow you to exchange changes you made to source code and
+collaborate with other people on the same project.
+
+## Authorization for merge requests
+
+There are two main ways to have a merge request flow with GitLab:
+
+1. Working with [protected branches][] in a single repository
+1. Working with forks of an authoritative project
+
+[Learn more about the authorization for merge requests.](authorization_for_merge_requests.md)
+
+## Cherry-pick changes
+
+Cherry-pick any commit in the UI by simply clicking the **Cherry-pick** button
+in a merged merge requests or a commit.
+
+[Learn more about cherry-picking changes.](cherry_pick_changes.md)
+
+## Merge when pipeline succeeds
+
+When reviewing a merge request that looks ready to merge but still has one or
+more CI jobs running, you can set it to be merged automatically when CI
+pipeline succeeds. This way, you don't have to wait for the pipeline to finish
+and remember to merge the request manually.
+
+[Learn more about merging when pipeline succeeds.](merge_when_pipeline_succeeds.md)
+
+## Resolve discussion comments in merge requests reviews
+
+Keep track of the progress during a code review with resolving comments.
+Resolving comments prevents you from forgetting to address feedback and lets
+you hide discussions that are no longer relevant.
+
+[Read more about resolving discussion comments in merge requests reviews.](merge_request_discussion_resolution.md)
+
+## Resolve conflicts
+
+When a merge request has conflicts, GitLab may provide the option to resolve
+those conflicts in the GitLab UI.
+
+[Learn more about resolving merge conflicts in the UI.](resolve_conflicts.md)
+
+## Revert changes
+
+GitLab implements Git's powerful feature to revert any commit with introducing
+a **Revert** button in merge requests and commit details.
+
+[Learn more about reverting changes in the UI](revert_changes.md)
+
+## Merge requests versions
+
+Every time you push to a branch that is tied to a merge request, a new version
+of merge request diff is created. When you visit a merge request that contains
+more than one pushes, you can select and compare the versions of those merge
+request diffs.
+
+[Read more about the merge requests versions.](versions.md)
+
+## Work In Progress merge requests
+
+To prevent merge requests from accidentally being accepted before they're
+completely ready, GitLab blocks the "Accept" button for merge requests that
+have been marked as a **Work In Progress**.
+
+[Learn more about settings a merge request as "Work In Progress".](work_in_progress_merge_requests.md)
+
+## Ignore whitespace changes in Merge Request diff view
+
+If you click the **Hide whitespace changes** button, you can see the diff
+without whitespace changes (if there are any). This is also working when on a
+specific commit page.
+
+![MR diff](img/merge_request_diff.png)
+
+>**Tip:**
+You can append `?w=1` while on the diffs page of a merge request to ignore any
+whitespace changes.
+
+## Tips
+
+Here are some tips that will help you be more efficient with merge requests in
+the command line.
+
+> **Note:**
+This section might move in its own document in the future.
+
+### Checkout merge requests locally
+
+A merge request contains all the history from a repository, plus the additional
+commits added to the branch associated with the merge request. Here's a few
+tricks to checkout a merge request locally.
+
+Please note that you can checkout a merge request locally even if the source
+project is a fork (even a private fork) of the target project.
+
+#### Checkout locally by adding a git alias
+
+Add the following alias to your `~/.gitconfig`:
+
+```
+[alias]
+ mr = !sh -c 'git fetch $1 merge-requests/$2/head:mr-$1-$2 && git checkout mr-$1-$2' -
+```
+
+Now you can check out a particular merge request from any repository and any
+remote. For example, to check out the merge request with ID 5 as shown in GitLab
+from the `upstream` remote, do:
+
+```
+git mr upstream 5
+```
+
+This will fetch the merge request into a local `mr-upstream-5` branch and check
+it out.
+
+#### Checkout locally by modifying `.git/config` for a given repository
+
+Locate the section for your GitLab remote in the `.git/config` file. It looks
+like this:
+
+```
+[remote "origin"]
+ url = https://gitlab.com/gitlab-org/gitlab-ce.git
+ fetch = +refs/heads/*:refs/remotes/origin/*
+```
+
+You can open the file with:
+
+```
+git config -e
+```
+
+Now add the following line to the above section:
+
+```
+fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/*
+```
+
+In the end, it should look like this:
+
+```
+[remote "origin"]
+ url = https://gitlab.com/gitlab-org/gitlab-ce.git
+ fetch = +refs/heads/*:refs/remotes/origin/*
+ fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/*
+```
+
+Now you can fetch all the merge requests:
+
+```
+git fetch origin
+
+...
+From https://gitlab.com/gitlab-org/gitlab-ce.git
+ * [new ref] refs/merge-requests/1/head -> origin/merge-requests/1
+ * [new ref] refs/merge-requests/2/head -> origin/merge-requests/2
+...
+```
+
+And to check out a particular merge request:
+
+```
+git checkout origin/merge-requests/1
+```
+
+[protected branches]: ../protected_branches.md
diff --git a/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md b/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md
index 75ad18b28cf..c63a408505f 100644
--- a/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md
+++ b/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md
@@ -1,8 +1,8 @@
# Merge When Pipeline Succeeds
When reviewing a merge request that looks ready to merge but still has one or
-more CI builds running, you can set it to be merged automatically when the
-builds pipeline succeeds. This way, you don't have to wait for the builds to
+more CI jobs running, you can set it to be merged automatically when the
+jobs pipeline succeeds. This way, you don't have to wait for the jobs to
finish and remember to merge the request manually.
![Enable](img/merge_when_build_succeeds_enable.png)
@@ -19,10 +19,10 @@ after all.
![Status](img/merge_when_build_succeeds_status.png)
When the pipeline succeeds, the merge request will automatically be merged.
-When the pipeline fails, the author gets a chance to retry any failed builds,
+When the pipeline fails, the author gets a chance to retry any failed jobs,
or to push new commits to fix the failure.
-When the builds are retried and succeed on the second try, the merge request
+When the jobs are retried and succeed on the second try, the merge request
will automatically be merged after all. When the merge request is updated with
new commits, the automatic merge is automatically canceled to allow the new
changes to be reviewed.
@@ -30,7 +30,7 @@ changes to be reviewed.
## Only allow merge requests to be merged if the pipeline succeeds
> **Note:**
-You need to have builds configured to enable this feature.
+You need to have jobs configured to enable this feature.
You can prevent merge requests from being merged if their pipeline did not succeed.
@@ -41,6 +41,6 @@ hit **Save** for the changes to take effect.
![Only allow merge if pipeline succeeds settings](img/merge_when_build_succeeds_only_if_succeeds_settings.png)
From now on, every time the pipeline fails you will not be able to merge the
-merge request from the UI, until you make all relevant builds pass.
+merge request from the UI, until you make all relevant jobs pass.
![Only allow merge if pipeline succeeds message](img/merge_when_build_succeeds_only_if_succeeds_msg.png)
diff --git a/doc/user/project/merge_requests/versions.md b/doc/user/project/merge_requests/versions.md
index 77eab7ba5e3..610250ccf12 100644
--- a/doc/user/project/merge_requests/versions.md
+++ b/doc/user/project/merge_requests/versions.md
@@ -1,6 +1,12 @@
# Merge requests versions
-> Will be [introduced][ce-5467] in GitLab 8.12.
+>**Notes:**
+- [Introduced][ce-5467] in GitLab 8.12.
+- Comments are disabled while viewing outdated merge versions or comparing to
+ versions other than base.
+- Merge request versions are based on push not on commit. So, if you pushed 5
+ commits in a single push, it will be a single option in the dropdown. If you
+ pushed 5 times, that will count for 5 options.
Every time you push to a branch that is tied to a merge request, a new version
of merge request diff is created. When you visit a merge request that contains
@@ -30,13 +36,4 @@ changes appears as a system note.
![Merge request versions system note](img/versions_system_note.png)
----
-
->**Notes:**
-- Comments are disabled while viewing outdated merge versions or comparing to
- versions other than base.
-- Merge request versions are based on push not on commit. So, if you pushed 5
- commits in a single push, it will be a single option in the dropdown. If you
- pushed 5 times, that will count for 5 options.
-
[ce-5467]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5467
diff --git a/doc/user/project/new_ci_build_permissions_model.md b/doc/user/project/new_ci_build_permissions_model.md
index 320faff65c5..5f631f63050 100644
--- a/doc/user/project/new_ci_build_permissions_model.md
+++ b/doc/user/project/new_ci_build_permissions_model.md
@@ -1,55 +1,55 @@
-# New CI build permissions model
+# New CI job permissions model
> Introduced in GitLab 8.12.
-GitLab 8.12 has a completely redesigned [build permissions] system. You can find
+GitLab 8.12 has a completely redesigned [job permissions] system. You can find
all discussion and all our concerns when choosing the current approach in issue
[#18994](https://gitlab.com/gitlab-org/gitlab-ce/issues/18994).
---
-Builds permissions should be tightly integrated with the permissions of a user
-who is triggering a build.
+Jobs permissions should be tightly integrated with the permissions of a user
+who is triggering a job.
The reasons to do it like that are:
- We already have a permissions system in place: group and project membership
of users.
-- We already fully know who is triggering a build (using `git push`, using the
+- We already fully know who is triggering a job (using `git push`, using the
web UI, executing triggers).
- We already know what user is allowed to do.
-- We use the user permissions for builds that are triggered by the user.
+- We use the user permissions for jobs that are triggered by the user.
- It opens a lot of possibilities to further enforce user permissions, like
allowing only specific users to access runners or use secure variables and
environments.
-- It is simple and convenient that your build can access everything that you
+- It is simple and convenient that your job can access everything that you
as a user have access to.
-- Short living unique tokens are now used, granting access for time of the build
+- Short living unique tokens are now used, granting access for time of the job
and maximizing security.
-With the new behavior, any build that is triggered by the user, is also marked
+With the new behavior, any job that is triggered by the user, is also marked
with their permissions. When a user does a `git push` or changes files through
the web UI, a new pipeline will be usually created. This pipeline will be marked
-as created be the pusher (local push or via the UI) and any build created in this
+as created be the pusher (local push or via the UI) and any job created in this
pipeline will have the permissions of the pusher.
This allows us to make it really easy to evaluate the access for all projects
that have [Git submodules][gitsub] or are using container images that the pusher
-would have access too. **The permission is granted only for time that build is
-running. The access is revoked after the build is finished.**
+would have access too. **The permission is granted only for time that job is
+running. The access is revoked after the job is finished.**
## Types of users
It is important to note that we have a few types of users:
-- **Administrators**: CI builds created by Administrators will not have access
+- **Administrators**: CI jobs created by Administrators will not have access
to all GitLab projects, but only to projects and container images of projects
that the administrator is a member of.That means that if a project is either
public or internal users have access anyway, but if a project is private, the
Administrator will have to be a member of it in order to have access to it
- via another project's build.
+ via another project's job.
-- **External users**: CI builds created by [external users][ext] will have
+- **External users**: CI jobs created by [external users][ext] will have
access only to projects to which user has at least reporter access. This
rules out accessing all internal projects by default,
@@ -57,46 +57,46 @@ This allows us to make the CI and permission system more trustworthy.
Let's consider the following scenario:
1. You are an employee of a company. Your company has a number of internal tools
- hosted in private repositories and you have multiple CI builds that make use
+ hosted in private repositories and you have multiple CI jobs that make use
of these repositories.
-2. You invite a new [external user][ext]. CI builds created by that user do not
+2. You invite a new [external user][ext]. CI jobs created by that user do not
have access to internal repositories, because the user also doesn't have the
access from within GitLab. You as an employee have to grant explicit access
for this user. This allows us to prevent from accidental data leakage.
-## Build token
+## Job token
-A unique build token is generated for each build and it allows the user to
+A unique job token is generated for each job and it allows the user to
access all projects that would be normally accessible to the user creating that
-build.
+job.
We try to make sure that this token doesn't leak by:
-1. Securing all API endpoints to not expose the build token.
-1. Masking the build token from build logs.
-1. Allowing to use the build token **only** when build is running.
+1. Securing all API endpoints to not expose the job token.
+1. Masking the job token from job logs.
+1. Allowing to use the job token **only** when job is running.
However, this brings a question about the Runners security. To make sure that
this token doesn't leak, you should also make sure that you configure
your Runners in the most possible secure way, by avoiding the following:
1. Any usage of Docker's `privileged` mode is risky if the machines are re-used.
-1. Using the `shell` executor since builds run on the same machine.
+1. Using the `shell` executor since jobs run on the same machine.
By using an insecure GitLab Runner configuration, you allow the rogue developers
-to steal the tokens of other builds.
+to steal the tokens of other jobs.
-## Build triggers
+## job triggers
-[Build triggers][triggers] do not support the new permission model.
-They continue to use the old authentication mechanism where the CI build
+[job triggers][triggers] do not support the new permission model.
+They continue to use the old authentication mechanism where the CI job
can access only its own sources. We plan to remove that limitation in one of
the upcoming releases.
## Before GitLab 8.12
-In versions before GitLab 8.12, all CI builds would use the CI Runner's token
+In versions before GitLab 8.12, all CI jobs would use the CI Runner's token
to checkout project sources.
The project's Runner's token was a token that you could find under the
@@ -105,7 +105,7 @@ project.
It could be used for registering new specific Runners assigned to the project
and to checkout project sources.
It could also be used with the GitLab Container Registry for that project,
-allowing pulling and pushing Docker images from within the CI build.
+allowing pulling and pushing Docker images from within the CI job.
---
@@ -115,7 +115,7 @@ GitLab would create a special checkout URL like:
https://gitlab-ci-token:<project-runners-token>/gitlab.com/gitlab-org/gitlab-ce.git
```
-And then the users could also use it in their CI builds all Docker related
+And then the users could also use it in their CI jobs all Docker related
commands to interact with GitLab Container Registry. For example:
```
@@ -125,7 +125,7 @@ docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN registry.gitlab.com
Using single token had multiple security implications:
- The token would be readable to anyone who had developer access to a project
- that could run CI builds, allowing the developer to register any specific
+ that could run CI jobs, allowing the developer to register any specific
Runner for that project.
- The token would allow to access only the project's sources, forbidding from
accessing any other projects.
@@ -133,12 +133,12 @@ Using single token had multiple security implications:
for registering specific runners and for accessing a project's container
registry with read-write permissions.
-All the above led to a new permission model for builds that was introduced
+All the above led to a new permission model for jobs that was introduced
with GitLab 8.12.
-## Making use of the new CI build permissions model
+## Making use of the new CI job permissions model
-With the new build permissions model, there is now an easy way to access all
+With the new job permissions model, there is now an easy way to access all
dependent source code in a project. That way, we can:
1. Access a project's [Git submodules][gitsub]
@@ -151,9 +151,9 @@ the container registry.
### Prerequisites to use the new permissions model
-With the new permissions model in place, there may be times that your build will
+With the new permissions model in place, there may be times that your job will
fail. This is most likely because your project tries to access other project's
-sources, and you don't have the appropriate permissions. In the build log look
+sources, and you don't have the appropriate permissions. In the job log look
for information about 403 or forbidden access messages.
In short here's what you need to do should you encounter any issues.
@@ -175,7 +175,7 @@ As a user:
- Make sure you are a member of the group or project you're trying to have
access to. As an Administrator, you can verify that by impersonating the user
- and retry the failing build in order to verify that everything is correct.
+ and retry the failing job in order to verify that everything is correct.
### Git submodules
@@ -199,9 +199,9 @@ Container Registries for private projects.
to pass a personal access token instead of your password in order to login to
GitLab's Container Registry.
-Your builds can access all container images that you would normally have access
+Your jobs can access all container images that you would normally have access
to. The only implication is that you can push to the Container Registry of the
-project for which the build is triggered.
+project for which the job is triggered.
This is how an example usage can look like:
@@ -213,7 +213,7 @@ test:
- docker run $CI_REGISTRY/group/other-project:latest
```
-[build permissions]: ../permissions.md#builds-permissions
+[job permissions]: ../permissions.md#jobs-permissions
[comment]: https://gitlab.com/gitlab-org/gitlab-ce/issues/22484#note_16648302
[ext]: ../permissions.md#external-users
[gitsub]: ../../ci/git_submodules.md
diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md
index b814e3fccb2..276fbd26835 100644
--- a/doc/user/project/pages/index.md
+++ b/doc/user/project/pages/index.md
@@ -14,6 +14,8 @@ deploy static pages for your individual projects, your user or your group.
Read [GitLab Pages on GitLab.com](#gitlab-pages-on-gitlab-com) for specific
information, if you are using GitLab.com to host your website.
+Read through [All you Need to Know About GitLab Pages][pages-index-guide] for a list of all learning materials we have prepared for GitLab Pages (webpages, articles, guides, blog posts, video tutorials).
+
## Getting started with GitLab Pages
> **Note:**
@@ -50,13 +52,13 @@ In brief, this is what you need to upload your website in GitLab Pages:
(ask your administrator). This is very important, so you should first make
sure you get that right.
1. Create a project
-1. Push a [`.gitlab-ci.yml` file](../ci/yaml/README.md) in the root directory
+1. Push a [`.gitlab-ci.yml` file][yaml] in the root directory
of your repository with a specific job named [`pages`][pages]
1. Set up a GitLab Runner to build your website
> **Note:**
-> If [shared runners](../ci/runners/README.md) are enabled by your GitLab
-> administrator, you should be able to use them instead of bringing your own.
+If [shared runners](../../../ci/runners/README.md) are enabled by your GitLab
+administrator, you should be able to use them instead of bringing your own.
### User or group Pages
@@ -89,23 +91,30 @@ GitLab Pages for projects can be created by both user and group accounts.
The steps to create a project page for a user or a group are identical:
1. Create a new project
-1. Push a [`.gitlab-ci.yml` file](../ci/yaml/README.md) in the root directory
+1. Push a [`.gitlab-ci.yml` file][yaml] in the root directory
of your repository with a specific job named [`pages`][pages].
1. Set up a GitLab Runner to build your website
A user's project will be served under `http(s)://username.example.io/projectname`
whereas a group's project under `http(s)://groupname.example.io/projectname`.
+## Quick Start
+
+Read through [GitLab Pages Quick Start Guide][pages-quick] or watch the video tutorial on
+[how to publish a website with GitLab Pages on GitLab.com from a forked project][video-pages-fork].
+
+See also [All you Need to Know About GitLab Pages][pages-index-guide] for a list with all the resources we have for GitLab Pages.
+
### Explore the contents of `.gitlab-ci.yml`
The key thing about GitLab Pages is the `.gitlab-ci.yml` file, something that
gives you absolute control over the build process. You can actually watch your
-website being built live by following the CI build traces.
+website being built live by following the CI job traces.
> **Note:**
> Before reading this section, make sure you familiarize yourself with GitLab CI
-> and the specific syntax of[`.gitlab-ci.yml`](../ci/yaml/README.md) by
-> following our [quick start guide](../ci/quick_start/README.md).
+> and the specific syntax of[`.gitlab-ci.yml`][yaml] by
+> following our [quick start guide].
To make use of GitLab Pages, the contents of `.gitlab-ci.yml` must follow the
rules below:
@@ -127,16 +136,16 @@ pages:
```
When the Runner reaches to build the `pages` job, it executes whatever is
-defined in the `script` parameter and if the build completes with a non-zero
+defined in the `script` parameter and if the job completes with a non-zero
exit status, it then uploads the `public/` directory to GitLab Pages.
The `public/` directory should contain all the static content of your website.
Depending on how you plan to publish your website, the steps defined in the
-[`script` parameter](../ci/yaml/README.md#script) may differ.
+[`script` parameter](../../../ci/yaml/README.md#script) may differ.
Be aware that Pages are by default branch/tag agnostic and their deployment
relies solely on what you specify in `.gitlab-ci.yml`. If you don't limit the
-`pages` job with the [`only` parameter](../ci/yaml/README.md#only-and-except),
+`pages` job with the [`only` parameter](../../../ci/yaml/README.md#only-and-except),
whenever a new commit is pushed to whatever branch or tag, the Pages will be
overwritten. In the example below, we limit the Pages to be deployed whenever
a commit is pushed only on the `master` branch:
@@ -211,11 +220,11 @@ pages: # the build job must be named pages
```
Here, we used the Docker executor and in the first line we specified the base
-image against which our builds will run.
+image against which our jobs will run.
You have to make sure that the generated static files are ultimately placed
under the `public` directory, that's why in the `script` section we run the
-`jekyll` command that builds the website and puts all content in the `public/`
+`jekyll` command that jobs the website and puts all content in the `public/`
directory. Depending on the static generator of your choice, this command will
differ. Search in the documentation of the static generator you will use if
there is an option to explicitly set the output directory. If there is not
@@ -237,7 +246,7 @@ get you started.
Remember that GitLab Pages are by default branch/tag agnostic and their
deployment relies solely on what you specify in `.gitlab-ci.yml`. You can limit
-the `pages` job with the [`only` parameter](../ci/yaml/README.md#only-and-except),
+the `pages` job with the [`only` parameter](../../../ci/yaml/README.md#only-and-except),
whenever a new commit is pushed to a branch that will be used specifically for
your pages.
@@ -403,7 +412,7 @@ don't have to create and edit HTML files manually. For example, Jekyll has the
### Can I download my generated pages?
-Sure. All you need to do is download the artifacts archive from the build page.
+Sure. All you need to do is download the artifacts archive from the job page.
### Can I use GitLab Pages if my project is private?
@@ -426,10 +435,15 @@ For a list of known issues, visit GitLab's [public issue tracker].
[ee-173]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173
[pages-daemon]: https://gitlab.com/gitlab-org/gitlab-pages
[gitlab ci]: https://about.gitlab.com/gitlab-ci
-[gitlab runner]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner
-[pages]: ../ci/yaml/README.md#pages
+[gitlab runner]: https://docs.gitlab.com/runner
+[pages]: ../../../ci/yaml/README.md#pages
+[yaml]: ../../../ci/yaml/README.md
[staticgen]: https://www.staticgen.com/
[pages-jekyll]: https://gitlab.com/pages/jekyll
[metarefresh]: https://en.wikipedia.org/wiki/Meta_refresh
[public issue tracker]: https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name=Pages
[ce-14605]: https://gitlab.com/gitlab-org/gitlab-ce/issues/14605
+[quick start guide]: ../../../ci/quick_start/README.md
+[pages-index-guide]: ../../../pages/index.md
+[pages-quick]: ../../../pages/getting_started_part_one.md
+[video-pages-fork]: https://youtu.be/TWqh9MtT4Bg
diff --git a/doc/user/project/pipelines/img/job_artifacts_browser.png b/doc/user/project/pipelines/img/job_artifacts_browser.png
new file mode 100644
index 00000000000..145fe156bbb
--- /dev/null
+++ b/doc/user/project/pipelines/img/job_artifacts_browser.png
Binary files differ
diff --git a/doc/user/project/pipelines/img/job_artifacts_browser_button.png b/doc/user/project/pipelines/img/job_artifacts_browser_button.png
new file mode 100644
index 00000000000..21072ce1248
--- /dev/null
+++ b/doc/user/project/pipelines/img/job_artifacts_browser_button.png
Binary files differ
diff --git a/doc/user/project/pipelines/img/job_artifacts_builds_page.png b/doc/user/project/pipelines/img/job_artifacts_builds_page.png
new file mode 100644
index 00000000000..13e039ba934
--- /dev/null
+++ b/doc/user/project/pipelines/img/job_artifacts_builds_page.png
Binary files differ
diff --git a/doc/user/project/pipelines/img/job_artifacts_pipelines_page.png b/doc/user/project/pipelines/img/job_artifacts_pipelines_page.png
new file mode 100644
index 00000000000..3ccce4f9bb4
--- /dev/null
+++ b/doc/user/project/pipelines/img/job_artifacts_pipelines_page.png
Binary files differ
diff --git a/doc/user/project/builds/img/build_latest_artifacts_browser.png b/doc/user/project/pipelines/img/job_latest_artifacts_browser.png
index c6d8856078b..c6d8856078b 100644
--- a/doc/user/project/builds/img/build_latest_artifacts_browser.png
+++ b/doc/user/project/pipelines/img/job_latest_artifacts_browser.png
Binary files differ
diff --git a/doc/user/project/pipelines/img/pipelines_settings_test_coverage.png b/doc/user/project/pipelines/img/pipelines_settings_test_coverage.png
index 2a99201e014..13ed69be810 100644
--- a/doc/user/project/pipelines/img/pipelines_settings_test_coverage.png
+++ b/doc/user/project/pipelines/img/pipelines_settings_test_coverage.png
Binary files differ
diff --git a/doc/user/project/pipelines/img/pipelines_test_coverage_mr_widget.png b/doc/user/project/pipelines/img/pipelines_test_coverage_mr_widget.png
index c166bb8bec8..fbcd612f3f2 100644
--- a/doc/user/project/pipelines/img/pipelines_test_coverage_mr_widget.png
+++ b/doc/user/project/pipelines/img/pipelines_test_coverage_mr_widget.png
Binary files differ
diff --git a/doc/user/project/pipelines/job_artifacts.md b/doc/user/project/pipelines/job_artifacts.md
new file mode 100644
index 00000000000..5ce99843301
--- /dev/null
+++ b/doc/user/project/pipelines/job_artifacts.md
@@ -0,0 +1,143 @@
+# Introduction to job artifacts
+
+>**Notes:**
+>- Since GitLab 8.2 and GitLab Runner 0.7.0, job artifacts that are created by
+ GitLab Runner are uploaded to GitLab and are downloadable as a single archive
+ (`tar.gz`) using the GitLab UI.
+>- Starting with GitLab 8.4 and GitLab Runner 1.0, the artifacts archive format
+ changed to `ZIP`, and it is now possible to browse its contents, with the added
+ ability of downloading the files separately.
+>- Starting with GitLab 8.17, builds are renamed to jobs.
+>- The artifacts browser will be available only for new artifacts that are sent
+ to GitLab using GitLab Runner version 1.0 and up. It will not be possible to
+ browse old artifacts already uploaded to GitLab.
+>- This is the user documentation. For the administration guide see
+ [administration/job_artifacts.md](../../../administration/job_artifacts.md).
+
+Artifacts is a list of files and directories which are attached to a job
+after it completes successfully. This feature is enabled by default in all
+GitLab installations.
+
+## Defining artifacts in `.gitlab-ci.yml`
+
+A simple example of using the artifacts definition in `.gitlab-ci.yml` would be
+the following:
+
+```yaml
+pdf:
+ script: xelatex mycv.tex
+ artifacts:
+ paths:
+ - mycv.pdf
+```
+
+A job named `pdf` calls the `xelatex` command in order to build a pdf file from
+the latex source file `mycv.tex`. We then define the `artifacts` paths which in
+turn are defined with the `paths` keyword. All paths to files and directories
+are relative to the repository that was cloned during the build.
+
+For more examples on artifacts, follow the artifacts reference in
+[`.gitlab-ci.yml` documentation](../../../ci/yaml/README.md#artifacts).
+
+## Browsing job artifacts
+
+After a job finishes, if you visit the job's specific page, you can see
+that there are two buttons. One is for downloading the artifacts archive and
+the other for browsing its contents.
+
+![Job artifacts browser button](img/job_artifacts_browser_button.png)
+
+---
+
+The archive browser shows the name and the actual file size of each file in the
+archive. If your artifacts contained directories, then you are also able to
+browse inside them.
+
+Below you can see how browsing looks like. In this case we have browsed inside
+the archive and at this point there is one directory and one HTML file.
+
+![Job artifacts browser](img/job_artifacts_browser.png)
+
+---
+
+## Downloading job artifacts
+
+If you need to download the whole archive, there are buttons in various places
+inside GitLab that make that possible.
+
+1. While on the pipelines page, you can see the download icon for each job's
+ artifacts archive in the right corner:
+
+ ![Job artifacts in Pipelines page](img/job_artifacts_pipelines_page.png)
+
+1. While on the **Jobs** page, you can see the download icon for each job's
+ artifacts archive in the right corner:
+
+ ![Job artifacts in Builds page](img/job_artifacts_builds_page.png)
+
+1. While inside a specific job, you are presented with a download button
+ along with the one that browses the archive:
+
+ ![Job artifacts browser button](img/job_artifacts_browser_button.png)
+
+1. And finally, when browsing an archive you can see the download button at
+ the top right corner:
+
+ ![Job artifacts browser](img/job_artifacts_browser.png)
+
+## Downloading the latest job artifacts
+
+It is possible to download the latest artifacts of a job via a well known URL
+so you can use it for scripting purposes.
+
+The structure of the URL to download the whole artifacts archive is the following:
+
+```
+https://example.com/<namespace>/<project>/builds/artifacts/<ref>/download?job=<job_name>
+```
+
+To download a single file from the artifacts use the following URL:
+
+```
+https://example.com/<namespace>/<project>/builds/artifacts/<ref>/file/<path_to_file>?job=<job_name>
+```
+
+For example, to download the latest artifacts of the job named `coverage` of
+the `master` branch of the `gitlab-ce` project that belongs to the `gitlab-org`
+namespace, the URL would be:
+
+```
+https://gitlab.com/gitlab-org/gitlab-ce/builds/artifacts/master/download?job=coverage
+```
+
+To download the file `coverage/index.html` from the same
+artifacts use the following URL:
+
+```
+https://gitlab.com/gitlab-org/gitlab-ce/builds/artifacts/master/file/coverage/index.html?job=coverage
+```
+
+There is also a URL to browse the latest job artifacts:
+
+```
+https://example.com/<namespace>/<project>/builds/artifacts/<ref>/browse?job=<job_name>
+```
+
+For example:
+
+```
+https://gitlab.com/gitlab-org/gitlab-ce/builds/artifacts/master/browse?job=coverage
+```
+
+The latest builds are also exposed in the UI in various places. Specifically,
+look for the download button in:
+
+- the main project's page
+- the branches page
+- the tags page
+
+If the latest job has failed to upload the artifacts, you can see that
+information in the UI.
+
+![Latest artifacts button](img/job_latest_artifacts_browser.png)
+
diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md
index 6cbcf3c400f..c398ac2eb25 100644
--- a/doc/user/project/pipelines/settings.md
+++ b/doc/user/project/pipelines/settings.md
@@ -35,7 +35,7 @@ if the job surpasses the threshold, it is marked as failed.
## Test coverage parsing
If you use test coverage in your code, GitLab can capture its output in the
-build log using a regular expression. In the pipelines settings, search for the
+job log using a regular expression. In the pipelines settings, search for the
"Test coverage parsing" section.
![Pipelines settings test coverage](img/pipelines_settings_test_coverage.png)
@@ -44,7 +44,7 @@ Leave blank if you want to disable it or enter a ruby regular expression. You
can use http://rubular.com to test your regex.
If the pipeline succeeds, the coverage is shown in the merge request widget and
-in the builds table.
+in the jobs table.
![MR widget coverage](img/pipelines_test_coverage_mr_widget.png)
@@ -62,9 +62,9 @@ pipelines** checkbox and save the changes.
## Badges
-In the pipelines settings page you can find build status and test coverage
+In the pipelines settings page you can find pipeline status and test coverage
badges for your project. The latest successful pipeline will be used to read
-the build status and test coverage values.
+the pipeline status and test coverage values.
Visit the pipelines settings page in your project to see the exact link to
your badges, as well as ways to embed the badge image in your HTML or Markdown
@@ -72,9 +72,9 @@ pages.
![Pipelines badges](img/pipelines_settings_badges.png)
-### Build status badge
+### Pipeline status badge
-Depending on the status of your build, a badge can have the following values:
+Depending on the status of your job, a badge can have the following values:
- running
- success
@@ -82,7 +82,7 @@ Depending on the status of your build, a badge can have the following values:
- skipped
- unknown
-You can access a build status badge image using the following link:
+You can access a pipeline status badge image using the following link:
```
https://example.gitlab.com/<namespace>/<project>/badges/<branch>/build.svg
@@ -91,7 +91,7 @@ https://example.gitlab.com/<namespace>/<project>/badges/<branch>/build.svg
### Test coverage report badge
GitLab makes it possible to define the regular expression for [coverage report],
-that each build log will be matched against. This means that each build in the
+that each job log will be matched against. This means that each job in the
pipeline can have the test coverage percentage value defined.
The test coverage badge can be accessed using following link:
diff --git a/doc/user/project/slash_commands.md b/doc/user/project/slash_commands.md
index 2fddd7c6503..ad5d51d34f2 100644
--- a/doc/user/project/slash_commands.md
+++ b/doc/user/project/slash_commands.md
@@ -14,7 +14,7 @@ do.
|:---------------------------|:-------------|
| `/close` | Close the issue or merge request |
| `/reopen` | Reopen the issue or merge request |
-| `/merge` | Merge (when build succeeds) |
+| `/merge` | Merge (when pipeline succeeds) |
| `/title <New title>` | Change title |
| `/assign @username` | Assign |
| `/unassign` | Remove assignee |
diff --git a/doc/user/snippets.md b/doc/user/snippets.md
new file mode 100644
index 00000000000..417360e08ac
--- /dev/null
+++ b/doc/user/snippets.md
@@ -0,0 +1,19 @@
+# Snippets
+
+Snippets are little bits of code or text.
+
+There are 2 types of snippets - project snippets and personal snippets.
+
+## Project snippets
+
+Project snippets are always related to a specific project - see [Project features](../workflow/project_features.md) for more information.
+
+## Personal snippets
+
+Personal snippets are not related to any project and can be created completely independently. There are 3 visibility levels that can be set (public, internal, private - see [Public Access](../public_access/public_access.md) for more information).
+
+## Downloading snippets
+
+You can download the raw content of a snippet.
+
+By default snippets will be downloaded with Linux-style line endings (`LF`). If you want to preserve the original line endings you need to add a parameter `line_ending=raw` (eg. `https://gitlab.com/snippets/SNIPPET_ID/raw?line_ending=raw`). In case a snippet was created using the GitLab web interface the original line ending is Windows-like (`CRLF`).
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index 0b6f00c6aa4..9e7ee47387c 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -27,7 +27,7 @@
- [Web Editor](../user/project/repository/web_editor.md)
- [Releases](releases.md)
- [Milestones](milestones.md)
-- [Merge Requests](../user/project/merge_requests.md)
+- [Merge Requests](../user/project/merge_requests/index.md)
- [Authorization for merge requests](../user/project/merge_requests/authorization_for_merge_requests.md)
- [Cherry-pick changes](../user/project/merge_requests/cherry_pick_changes.md)
- [Merge when pipeline succeeds](../user/project/merge_requests/merge_when_pipeline_succeeds.md)
@@ -39,3 +39,4 @@
- [Manage large binaries with Git LFS](lfs/manage_large_binaries_with_git_lfs.md)
- [Importing from SVN, GitHub, Bitbucket, etc](importing/README.md)
- [Todos](todos.md)
+- [Snippets](../user/snippets.md)
diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md
index c228ea72f22..4889e3ec50c 100644
--- a/doc/workflow/gitlab_flow.md
+++ b/doc/workflow/gitlab_flow.md
@@ -67,7 +67,7 @@ With GitLab flow we offer additional guidance for these questions.
![Master branch and production branch with arrow that indicate deployments](production_branch.png)
GitHub flow does assume you are able to deploy to production every time you merge a feature branch.
-This is possible for SaaS applications but are many cases where this is not possible.
+This is possible for SaaS applications but there are many cases where this is not possible.
One would be a situation where you are not in control of the exact release moment, for example an iOS application that needs to pass App Store validation.
Another example is when you have deployment windows (workdays from 10am to 4pm when the operations team is at full capacity) but you also merge code at other times.
In these cases you can make a production branch that reflects the deployed code.
diff --git a/doc/workflow/groups.md b/doc/workflow/groups.md
index a693cc3d0fd..6237a5d5e18 100644
--- a/doc/workflow/groups.md
+++ b/doc/workflow/groups.md
@@ -23,7 +23,7 @@ You can use the 'New project' button to add a project to the new group.
## Transferring an existing project into a group
-You can transfer an existing project into a group you own from the project settings page.
+You can transfer an existing project into a group you own from the project settings page. The option to transfer a project is only available if you are the Owner of the project.
First scroll down to the 'Dangerous settings' and click 'Show them to me'.
Now you can pick any of the groups you manage as the new namespace for the group.
diff --git a/doc/workflow/importing/import_projects_from_github.md b/doc/workflow/importing/import_projects_from_github.md
index cdacef9832f..aece4ab34ba 100644
--- a/doc/workflow/importing/import_projects_from_github.md
+++ b/doc/workflow/importing/import_projects_from_github.md
@@ -60,8 +60,7 @@ If the [GitHub integration][gh-import] is enabled by your GitLab administrator,
you can use it instead of the personal access token.
1. First you may want to connect your GitHub account to GitLab in order for
- the username mapping to be correct. Follow the [social sign-in] documentation
- on how to do so.
+ the username mapping to be correct.
1. Once you connect GitHub, click the **List your GitHub repositories** button
and you will be redirected to GitHub for permission to access your projects.
1. After accepting, you'll be automatically redirected to the importer.
@@ -115,4 +114,3 @@ if you have the privileges to do so.
[gh-import]: ../../integration/github.md "GitHub integration"
[gh-integration]: #authorize-access-to-your-repositories-using-the-github-integration
[gh-token]: #authorize-access-to-your-repositories-using-a-personal-access-token
-[social sign-in]: ../../profile/account/social_sign_in.md
diff --git a/doc/workflow/shortcuts.md b/doc/workflow/shortcuts.md
index 36516883ef6..2a5e622dc7d 100644
--- a/doc/workflow/shortcuts.md
+++ b/doc/workflow/shortcuts.md
@@ -45,7 +45,7 @@ You can see GitLab's keyboard shortcuts by using 'shift + ?'
| <kbd>g</kbd> + <kbd>e</kbd> | Go to the project's activity feed |
| <kbd>g</kbd> + <kbd>f</kbd> | Go to files |
| <kbd>g</kbd> + <kbd>c</kbd> | Go to commits |
-| <kbd>g</kbd> + <kbd>b</kbd> | Go to builds |
+| <kbd>g</kbd> + <kbd>b</kbd> | Go to jobs |
| <kbd>g</kbd> + <kbd>n</kbd> | Go to network graph |
| <kbd>g</kbd> + <kbd>g</kbd> | Go to graphs |
| <kbd>g</kbd> + <kbd>i</kbd> | Go to issues |
@@ -73,4 +73,4 @@ You can see GitLab's keyboard shortcuts by using 'shift + ?'
| <kbd>m</kbd> | Change milestone |
| <kbd>r</kbd> | Reply (quoting selected text) |
| <kbd>e</kbd> | Edit issue/merge request |
-| <kbd>l</kbd> | Change label | \ No newline at end of file
+| <kbd>l</kbd> | Change label |
diff --git a/doc/workflow/todos.md b/doc/workflow/todos.md
index 1a8fc39bb33..4b0fba842e9 100644
--- a/doc/workflow/todos.md
+++ b/doc/workflow/todos.md
@@ -16,7 +16,8 @@ in a simple dashboard.
You can quickly access the Todos dashboard using the bell icon next to the
search bar in the upper right corner. The number in blue is the number of Todos
-you still have open.
+you still have open if the count is < 100, else it's 99+. The exact number
+will still be shown in the body of the _To do_ tab.
![Todos icon](img/todos_icon.png)
@@ -27,11 +28,34 @@ A Todo appears in your Todos dashboard when:
- an issue or merge request is assigned to you,
- you are `@mentioned` in an issue or merge request, be it the description of
the issue/merge request or in a comment,
-- build in the CI pipeline running for your merge request failed, but this
- build is not allowed to fail.
+- a job in the CI pipeline running for your merge request failed, but this
+ job is not allowed to fail.
>**Note:** Commenting on a commit will _not_ trigger a Todo.
+### Directly addressed Todos
+
+> [Introduced][ce-7926] in GitLab 9.0.
+
+If you are mentioned at the start of a line, the todo you receive will be listed
+as 'directly addressed'. For instance, in this comment:
+
+```markdown
+@alice What do you think? cc: @bob
+
+- @carol can you please have a look?
+
+>>>
+@dan what do you think?
+>>>
+
+@erin @frank thank you!
+```
+
+The people receiving directly addressed todos are `@alice`, `@erin`, and
+`@frank`. Directly addressed todos only differ from mention todos in their type,
+for filtering; otherwise, they appear as normal.
+
### Manually creating a Todo
You can also add an issue or merge request to your Todos dashboard by clicking
@@ -85,8 +109,9 @@ There are four kinds of filters you can use on your Todos dashboard.
| Project | Filter by project |
| Author | Filter by the author that triggered the Todo |
| Type | Filter by issue or merge request |
-| Action | Filter by the action that triggered the Todo (Assigned or Mentioned)|
+| Action | Filter by the action that triggered the Todo |
You can also filter by more than one of these at the same time.
[ce-2817]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2817
+[ce-7926]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7926
diff --git a/features/dashboard/issues.feature b/features/dashboard/issues.feature
deleted file mode 100644
index 99dad88a402..00000000000
--- a/features/dashboard/issues.feature
+++ /dev/null
@@ -1,21 +0,0 @@
-@dashboard
-Feature: Dashboard Issues
- Background:
- Given I sign in as a user
- And I have authored issues
- And I have assigned issues
- And I have other issues
- And I visit dashboard issues page
-
- Scenario: I should see assigned issues
- Then I should see issues assigned to me
-
- @javascript
- Scenario: I should see authored issues
- When I click "Authored by me" link
- Then I should see issues authored by me
-
- @javascript
- Scenario: I should see all issues
- When I click "All" link
- Then I should see all issues
diff --git a/features/project/labels.feature b/features/project/labels.feature
deleted file mode 100644
index 955bc3d8b1b..00000000000
--- a/features/project/labels.feature
+++ /dev/null
@@ -1,15 +0,0 @@
-@labels
-Feature: Labels
- Background:
- Given I sign in as a user
- And I own project "Shop"
- And project "Shop" has labels: "bug", "feature", "enhancement"
- When I visit project "Shop" labels page
-
- @javascript
- Scenario: I can subscribe to a label
- Then I should see that I am not subscribed to the "bug" label
- When I click button "Subscribe" for the "bug" label
- Then I should see that I am subscribed to the "bug" label
- When I click button "Unsubscribe" for the "bug" label
- Then I should see that I am not subscribed to the "bug" label
diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature
index 5aa592e9067..bcde497553b 100644
--- a/features/project/merge_requests.feature
+++ b/features/project/merge_requests.feature
@@ -294,13 +294,6 @@ Feature: Project Merge Requests
Then I should see the Markdown write tab
@javascript
- Scenario: I search merge request
- Given I click link "All"
- When I fill in merge request search with "Fe"
- Then I should see "Feature NS-03" in merge requests
- And I should not see "Bug NS-04" in merge requests
-
- @javascript
Scenario: I can unsubscribe from merge request
Given I visit merge request page "Bug NS-04"
Then I should see that I am subscribed
diff --git a/features/project/merge_requests/revert.feature b/features/project/merge_requests/revert.feature
index d767b088883..ec6666f227f 100644
--- a/features/project/merge_requests/revert.feature
+++ b/features/project/merge_requests/revert.feature
@@ -5,6 +5,7 @@ Feature: Revert Merge Requests
And I am signed in as a developer of the project
And I am on the Merge Request detail page
And I click on Accept Merge Request
+ And I am on the Merge Request detail page
@javascript
Scenario: I revert a merge request
diff --git a/features/snippets/user.feature b/features/snippets/user.feature
deleted file mode 100644
index 5b5dadb7b39..00000000000
--- a/features/snippets/user.feature
+++ /dev/null
@@ -1,34 +0,0 @@
-@snippets
-Feature: Snippets User
- Background:
- Given I sign in as a user
- And I have public "Personal snippet one" snippet
- And I have private "Personal snippet private" snippet
- And I have internal "Personal snippet internal" snippet
-
- Scenario: I should see all my snippets
- Given I visit my snippets page
- Then I should see "Personal snippet one" in snippets
- And I should see "Personal snippet private" in snippets
- And I should see "Personal snippet internal" in snippets
-
- Scenario: I can see only my private snippets
- Given I visit my snippets page
- And I click "Private" filter
- Then I should not see "Personal snippet one" in snippets
- And I should not see "Personal snippet internal" in snippets
- And I should see "Personal snippet private" in snippets
-
- Scenario: I can see only my public snippets
- Given I visit my snippets page
- And I click "Public" filter
- Then I should see "Personal snippet one" in snippets
- And I should not see "Personal snippet private" in snippets
- And I should not see "Personal snippet internal" in snippets
-
- Scenario: I can see only my internal snippets
- Given I visit my snippets page
- And I click "Internal" filter
- Then I should see "Personal snippet internal" in snippets
- And I should not see "Personal snippet private" in snippets
- And I should not see "Personal snippet one" in snippets
diff --git a/features/steps/dashboard/issues.rb b/features/steps/dashboard/issues.rb
deleted file mode 100644
index 4e15d79ae74..00000000000
--- a/features/steps/dashboard/issues.rb
+++ /dev/null
@@ -1,91 +0,0 @@
-class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedPaths
- include Select2Helper
-
- step 'I should see issues assigned to me' do
- should_see(assigned_issue)
- should_not_see(authored_issue)
- should_not_see(other_issue)
- end
-
- step 'I should see issues authored by me' do
- should_see(authored_issue)
- should_see(authored_issue_on_public_project)
- should_not_see(assigned_issue)
- should_not_see(other_issue)
- end
-
- step 'I should see all issues' do
- should_see(authored_issue)
- should_see(assigned_issue)
- should_see(other_issue)
- end
-
- step 'I have authored issues' do
- authored_issue
- authored_issue_on_public_project
- end
-
- step 'I have assigned issues' do
- assigned_issue
- end
-
- step 'I have other issues' do
- other_issue
- end
-
- step 'I click "Authored by me" link' do
- find("#assignee_id").set("")
- find(".js-author-search", match: :first).click
- find(".dropdown-menu-author li a", match: :first, text: current_user.to_reference).click
- end
-
- step 'I click "All" link' do
- find(".js-author-search").click
- expect(page).to have_selector(".dropdown-menu-author li a")
- find(".dropdown-menu-author li a", match: :first).click
- expect(page).not_to have_selector(".dropdown-menu-author li a")
-
- find(".js-assignee-search").click
- expect(page).to have_selector(".dropdown-menu-assignee li a")
- find(".dropdown-menu-assignee li a", match: :first).click
- expect(page).not_to have_selector(".dropdown-menu-assignee li a")
- end
-
- def should_see(issue)
- expect(page).to have_content(issue.title[0..10])
- end
-
- def should_not_see(issue)
- expect(page).not_to have_content(issue.title[0..10])
- end
-
- def assigned_issue
- @assigned_issue ||= create :issue, assignee: current_user, project: project
- end
-
- def authored_issue
- @authored_issue ||= create :issue, author: current_user, project: project
- end
-
- def other_issue
- @other_issue ||= create :issue, project: project
- end
-
- def authored_issue_on_public_project
- @authored_issue_on_public_project ||= create :issue, author: current_user, project: public_project
- end
-
- def project
- @project ||= begin
- project = create(:empty_project)
- project.team << [current_user, :master]
- project
- end
- end
-
- def public_project
- @public_project ||= create(:empty_project, :public)
- end
-end
diff --git a/features/steps/dashboard/todos.rb b/features/steps/dashboard/todos.rb
index 2bbc43b491f..eb906a55a83 100644
--- a/features/steps/dashboard/todos.rb
+++ b/features/steps/dashboard/todos.rb
@@ -47,7 +47,7 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
page.within('.todos-pending-count') { expect(page).to have_content '3' }
expect(page).to have_content 'To do 3'
expect(page).to have_content 'Done 1'
- should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference(full: true)}"
+ should_see_todo(1, "John Doe assigned you merge request #{merge_request.to_reference(full: true)}", merge_request.title, state: :done_reversible)
end
step 'I mark all todos as done' do
@@ -71,7 +71,7 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
click_link 'Done 1'
expect(page).to have_link project.name_with_namespace
- should_see_todo(1, "John Doe assigned you merge request #{merge_request.to_reference(full: true)}", merge_request.title, false)
+ should_see_todo(1, "John Doe assigned you merge request #{merge_request.to_reference(full: true)}", merge_request.title, state: :done_irreversible)
end
step 'I should see all todos marked as done' do
@@ -81,10 +81,10 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
click_link 'Done 4'
expect(page).to have_link project.name_with_namespace
- should_see_todo(1, "John Doe assigned you merge request #{merge_request_reference}", merge_request.title, false)
- should_see_todo(2, "John Doe mentioned you on issue #{issue_reference}", "#{current_user.to_reference} Wdyt?", false)
- should_see_todo(3, "John Doe assigned you issue #{issue_reference}", issue.title, false)
- should_see_todo(4, "Mary Jane mentioned you on issue #{issue_reference}", issue.title, false)
+ should_see_todo(1, "John Doe assigned you merge request #{merge_request_reference}", merge_request.title, state: :done_irreversible)
+ should_see_todo(2, "John Doe mentioned you on issue #{issue_reference}", "#{current_user.to_reference} Wdyt?", state: :done_irreversible)
+ should_see_todo(3, "John Doe assigned you issue #{issue_reference}", issue.title, state: :done_irreversible)
+ should_see_todo(4, "Mary Jane mentioned you on issue #{issue_reference}", issue.title, state: :done_irreversible)
end
step 'I filter by "Enterprise"' do
@@ -140,15 +140,20 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
page.should have_css('.identifier', text: 'Merge Request !1')
end
- def should_see_todo(position, title, body, pending = true)
+ def should_see_todo(position, title, body, state: :pending)
page.within(".todo:nth-child(#{position})") do
expect(page).to have_content title
expect(page).to have_content body
- if pending
+ if state == :pending
expect(page).to have_link 'Done'
- else
+ elsif state == :done_reversible
+ expect(page).to have_link 'Undo'
+ elsif state == :done_irreversible
+ expect(page).not_to have_link 'Undo'
expect(page).not_to have_link 'Done'
+ else
+ raise 'Invalid state given, valid states: :pending, :done_reversible, :done_irreversible'
end
end
end
diff --git a/features/steps/group/milestones.rb b/features/steps/group/milestones.rb
index 70e23098dde..20204ad8654 100644
--- a/features/steps/group/milestones.rb
+++ b/features/steps/group/milestones.rb
@@ -5,9 +5,7 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
include SharedUser
step 'I click on group milestones' do
- page.within('.layout-nav') do
- click_link 'Milestones'
- end
+ visit group_milestones_path('owned')
end
step 'I should see group milestones index page has no milestones' do
diff --git a/features/steps/project/builds/artifacts.rb b/features/steps/project/builds/artifacts.rb
index 055fca036d3..be0f6eee55a 100644
--- a/features/steps/project/builds/artifacts.rb
+++ b/features/steps/project/builds/artifacts.rb
@@ -76,7 +76,7 @@ class Spinach::Features::ProjectBuildsArtifacts < Spinach::FeatureSteps
base64_params = send_data.sub(/\Aartifacts\-entry:/, '')
params = JSON.parse(Base64.urlsafe_decode64(base64_params))
- expect(params.keys).to eq(['Archive', 'Entry'])
+ expect(params.keys).to eq(%w(Archive Entry))
expect(params['Archive']).to end_with('build_artifacts.zip')
expect(params['Entry']).to eq(Base64.encode64('ci_artifacts.txt'))
end
diff --git a/features/steps/project/labels.rb b/features/steps/project/labels.rb
deleted file mode 100644
index dbeb07c78db..00000000000
--- a/features/steps/project/labels.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-class Spinach::Features::Labels < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedIssuable
- include SharedProject
- include SharedPaths
-
- step 'And I visit project "Shop" labels page' do
- visit namespace_project_labels_path(project.namespace, project)
- end
-
- step 'I should see that I am subscribed to the "bug" label' do
- expect(subscribe_button).to have_content 'Unsubscribe'
- end
-
- step 'I should see that I am not subscribed to the "bug" label' do
- expect(subscribe_button).to have_content 'Subscribe'
- end
-
- step 'I click button "Unsubscribe" for the "bug" label' do
- subscribe_button.click
- end
-
- step 'I click button "Subscribe" for the "bug" label' do
- subscribe_button.click
- end
-
- private
-
- def subscribe_button
- first('.js-subscribe-button', visible: true)
- end
-end
diff --git a/features/steps/shared/builds.rb b/features/steps/shared/builds.rb
index d008a8a26af..5bc3a1f5ac4 100644
--- a/features/steps/shared/builds.rb
+++ b/features/steps/shared/builds.rb
@@ -11,7 +11,7 @@ module SharedBuilds
step 'project has a recent build' do
@pipeline = create(:ci_empty_pipeline, project: @project, sha: @project.commit.sha, ref: 'master')
- @build = create(:ci_build_with_coverage, pipeline: @pipeline)
+ @build = create(:ci_build, :coverage, pipeline: @pipeline)
end
step 'recent build is successful' do
diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb
index 79dde620265..3d9cedf5c2d 100644
--- a/features/steps/shared/issuable.rb
+++ b/features/steps/shared/issuable.rb
@@ -153,7 +153,7 @@ module SharedIssuable
case type
when :issue
- attrs.merge!(project: project)
+ attrs[:project] = project
when :merge_request
attrs.merge!(
source_project: project,
diff --git a/features/steps/snippets/user.rb b/features/steps/snippets/user.rb
deleted file mode 100644
index 997c605bce2..00000000000
--- a/features/steps/snippets/user.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-class Spinach::Features::SnippetsUser < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedPaths
- include SharedSnippet
-
- step 'I visit my snippets page' do
- visit dashboard_snippets_path
- end
-
- step 'I should see "Personal snippet one" in snippets' do
- expect(page).to have_content "Personal snippet one"
- end
-
- step 'I should see "Personal snippet private" in snippets' do
- expect(page).to have_content "Personal snippet private"
- end
-
- step 'I should see "Personal snippet internal" in snippets' do
- expect(page).to have_content "Personal snippet internal"
- end
-
- step 'I should not see "Personal snippet one" in snippets' do
- expect(page).not_to have_content "Personal snippet one"
- end
-
- step 'I should not see "Personal snippet private" in snippets' do
- expect(page).not_to have_content "Personal snippet private"
- end
-
- step 'I should not see "Personal snippet internal" in snippets' do
- expect(page).not_to have_content "Personal snippet internal"
- end
-
- step 'I click "Internal" filter' do
- page.within('.snippet-scope-menu') do
- click_link "Internal"
- end
- end
-
- step 'I click "Private" filter' do
- page.within('.snippet-scope-menu') do
- click_link "Private"
- end
- end
-
- step 'I click "Public" filter' do
- page.within('.snippet-scope-menu') do
- click_link "Public"
- end
- end
-
- def snippet
- @snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one")
- end
-end
diff --git a/lib/additional_email_headers_interceptor.rb b/lib/additional_email_headers_interceptor.rb
new file mode 100644
index 00000000000..2358fa6bbfd
--- /dev/null
+++ b/lib/additional_email_headers_interceptor.rb
@@ -0,0 +1,8 @@
+class AdditionalEmailHeadersInterceptor
+ def self.delivering_email(message)
+ message.headers(
+ 'Auto-Submitted' => 'auto-generated',
+ 'X-Auto-Response-Suppress' => 'All'
+ )
+ end
+end
diff --git a/lib/api/api.rb b/lib/api/api.rb
index eb9792680ff..dc732012a33 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -5,11 +5,28 @@ module API
version %w(v3 v4), using: :path
version 'v3', using: :path do
+ mount ::API::V3::Boards
+ mount ::API::V3::Branches
+ mount ::API::V3::Commits
mount ::API::V3::DeployKeys
+ mount ::API::V3::Files
+ mount ::API::V3::Groups
mount ::API::V3::Issues
+ mount ::API::V3::Labels
+ mount ::API::V3::Members
+ mount ::API::V3::MergeRequestDiffs
mount ::API::V3::MergeRequests
+ mount ::API::V3::Notes
+ mount ::API::V3::ProjectHooks
mount ::API::V3::Projects
mount ::API::V3::ProjectSnippets
+ mount ::API::V3::Repositories
+ mount ::API::V3::Subscriptions
+ mount ::API::V3::SystemHooks
+ mount ::API::V3::Tags
+ mount ::API::V3::Todos
+ mount ::API::V3::Templates
+ mount ::API::V3::Users
end
before { allow_access_with_scope :api }
diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb
index df6db140d0e..c11f8529183 100644
--- a/lib/api/api_guard.rb
+++ b/lib/api/api_guard.rb
@@ -6,7 +6,7 @@ module API
module APIGuard
extend ActiveSupport::Concern
- PRIVATE_TOKEN_HEADER = "HTTP_PRIVATE_TOKEN"
+ PRIVATE_TOKEN_HEADER = "HTTP_PRIVATE_TOKEN".freeze
PRIVATE_TOKEN_PARAM = :private_token
included do |base|
@@ -114,8 +114,8 @@ module API
private
def install_error_responders(base)
- error_classes = [ MissingTokenError, TokenNotFoundError,
- ExpiredError, RevokedError, InsufficientScopeError]
+ error_classes = [MissingTokenError, TokenNotFoundError,
+ ExpiredError, RevokedError, InsufficientScopeError]
base.send :rescue_from, *error_classes, oauth2_bearer_token_error_handler
end
diff --git a/lib/api/award_emoji.rb b/lib/api/award_emoji.rb
index 58a4df54bea..301271118d4 100644
--- a/lib/api/award_emoji.rb
+++ b/lib/api/award_emoji.rb
@@ -3,7 +3,7 @@ module API
include PaginationParams
before { authenticate! }
- AWARDABLES = %w[issue merge_request snippet]
+ AWARDABLES = %w[issue merge_request snippet].freeze
resource :projects do
AWARDABLES.each do |awardable_type|
@@ -15,7 +15,8 @@ module API
requires :"#{awardable_id_string}", type: Integer, desc: "The ID of an Issue, Merge Request or Snippet"
end
- [ ":id/#{awardable_string}/:#{awardable_id_string}/award_emoji",
+ [
+ ":id/#{awardable_string}/:#{awardable_id_string}/award_emoji",
":id/#{awardable_string}/:#{awardable_id_string}/notes/:note_id/award_emoji"
].each do |endpoint|
@@ -28,8 +29,8 @@ module API
end
get endpoint do
if can_read_awardable?
- awards = paginate(awardable.award_emoji)
- present awards, with: Entities::AwardEmoji
+ awards = awardable.award_emoji
+ present paginate(awards), with: Entities::AwardEmoji
else
not_found!("Award Emoji")
end
diff --git a/lib/api/boards.rb b/lib/api/boards.rb
index 13752eb4947..f4226e5a89d 100644
--- a/lib/api/boards.rb
+++ b/lib/api/boards.rb
@@ -1,6 +1,7 @@
module API
- # Boards API
class Boards < Grape::API
+ include PaginationParams
+
before { authenticate! }
params do
@@ -11,9 +12,12 @@ module API
detail 'This feature was introduced in 8.13'
success Entities::Board
end
+ params do
+ use :pagination
+ end
get ':id/boards' do
authorize!(:read_board, user_project)
- present user_project.boards, with: Entities::Board
+ present paginate(user_project.boards), with: Entities::Board
end
params do
@@ -40,9 +44,12 @@ module API
detail 'Does not include `done` list. This feature was introduced in 8.13'
success Entities::List
end
+ params do
+ use :pagination
+ end
get '/lists' do
authorize!(:read_board, user_project)
- present board_lists, with: Entities::List
+ present paginate(board_lists), with: Entities::List
end
desc 'Get a list of a project board' do
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index be659fa4a6a..34f136948c2 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -1,8 +1,9 @@
require 'mime/types'
module API
- # Projects API
class Branches < Grape::API
+ include PaginationParams
+
before { authenticate! }
before { authorize! :download_code, user_project }
@@ -13,10 +14,13 @@ module API
desc 'Get a project repository branches' do
success Entities::RepoBranch
end
+ params do
+ use :pagination
+ end
get ":id/repository/branches" do
- branches = user_project.repository.branches.sort_by(&:name)
+ branches = ::Kaminari.paginate_array(user_project.repository.branches.sort_by(&:name))
- present branches, with: Entities::RepoBranch, project: user_project
+ present paginate(branches), with: Entities::RepoBranch, project: user_project
end
desc 'Get a single branch' do
@@ -84,7 +88,7 @@ module API
branch = user_project.repository.find_branch(params[:branch])
not_found!("Branch") unless branch
protected_branch = user_project.protected_branches.find_by(name: branch.name)
- protected_branch.destroy if protected_branch
+ protected_branch&.destroy
present branch, with: Entities::RepoBranch, project: user_project
end
@@ -93,13 +97,13 @@ module API
success Entities::RepoBranch
end
params do
- requires :branch_name, type: String, desc: 'The name of the branch'
+ requires :branch, type: String, desc: 'The name of the branch'
requires :ref, type: String, desc: 'Create branch from commit sha or existing branch'
end
post ":id/repository/branches" do
authorize_push_project
result = CreateBranchService.new(user_project, current_user).
- execute(params[:branch_name], params[:ref])
+ execute(params[:branch], params[:ref])
if result[:status] == :success
present result[:branch],
@@ -122,7 +126,7 @@ module API
if result[:status] == :success
{
- branch_name: params[:branch]
+ branch: params[:branch]
}
else
render_api_error!(result[:message], result[:return_code])
@@ -133,7 +137,7 @@ module API
delete ":id/repository/merged_branches" do
DeleteMergedBranchesService.new(user_project, current_user).async_execute
- status(200)
+ accepted!
end
end
end
diff --git a/lib/api/builds.rb b/lib/api/builds.rb
index 44fe0fc4a95..5b76913fe45 100644
--- a/lib/api/builds.rb
+++ b/lib/api/builds.rb
@@ -11,7 +11,7 @@ module API
helpers do
params :optional_scope do
optional :scope, types: [String, Array[String]], desc: 'The scope of builds to show',
- values: ['pending', 'running', 'failed', 'success', 'canceled'],
+ values: ::CommitStatus::AVAILABLE_STATUSES,
coerce_with: ->(scope) {
if scope.is_a?(String)
[scope]
diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb
index b6e6820c3f4..dba0831664c 100644
--- a/lib/api/commit_statuses.rb
+++ b/lib/api/commit_statuses.rb
@@ -40,12 +40,13 @@ module API
requires :id, type: String, desc: 'The ID of a project'
requires :sha, type: String, desc: 'The commit hash'
requires :state, type: String, desc: 'The state of the status',
- values: ['pending', 'running', 'success', 'failed', 'canceled']
+ values: %w(pending running success failed canceled)
optional :ref, type: String, desc: 'The ref'
optional :target_url, type: String, desc: 'The target URL to associate with this status'
optional :description, type: String, desc: 'A short description of the status'
optional :name, type: String, desc: 'A string label to differentiate this status from the status of other systems. Default: "default"'
optional :context, type: String, desc: 'A string label to differentiate this status from the status of other systems. Default: "default"'
+ optional :coverage, type: Float, desc: 'The total code coverage'
end
post ':id/statuses/:sha' do
authorize! :create_commit_status, user_project
@@ -75,7 +76,8 @@ module API
name: name,
ref: ref,
target_url: params[:target_url],
- description: params[:description]
+ description: params[:description],
+ coverage: params[:coverage]
)
render_validation_error!(status) if status.invalid?
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 2fefe760d24..9ce396d4660 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -16,16 +16,13 @@ module API
end
params do
optional :ref_name, type: String, desc: 'The name of a repository branch or tag, if not given the default branch is used'
- optional :since, type: String, desc: 'Only commits after or in this date will be returned'
- optional :until, type: String, desc: 'Only commits before or in this date will be returned'
+ optional :since, type: DateTime, desc: 'Only commits after or on this date will be returned'
+ optional :until, type: DateTime, desc: 'Only commits before or on this date will be returned'
optional :page, type: Integer, default: 0, desc: 'The page for pagination'
optional :per_page, type: Integer, default: 20, desc: 'The number of results per page'
optional :path, type: String, desc: 'The file path'
end
get ":id/repository/commits" do
- # TODO remove the next line for 9.0, use DateTime type in the params block
- datetime_attributes! :since, :until
-
ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
offset = params[:page] * params[:per_page]
@@ -44,7 +41,7 @@ module API
detail 'This feature was introduced in GitLab 8.13'
end
params do
- requires :branch_name, type: String, desc: 'The name of branch'
+ requires :branch, type: String, desc: 'The name of branch'
requires :commit_message, type: String, desc: 'Commit message'
requires :actions, type: Array[Hash], desc: 'Actions to perform in commit'
optional :author_email, type: String, desc: 'Author email for commit'
@@ -53,9 +50,8 @@ module API
post ":id/repository/commits" do
authorize! :push_code, user_project
- attrs = declared_params
- attrs[:start_branch] = attrs[:branch_name]
- attrs[:target_branch] = attrs[:branch_name]
+ attrs = declared_params.merge(start_branch: declared_params[:branch], target_branch: declared_params[:branch])
+
attrs[:actions].map! do |action|
action[:action] = action[:action].to_sym
action[:file_path].slice!(0) if action[:file_path] && action[:file_path].start_with?('/')
@@ -114,7 +110,7 @@ module API
commit = user_project.commit(params[:sha])
not_found! 'Commit' unless commit
- notes = Note.where(commit_id: commit.id).order(:created_at)
+ notes = user_project.notes.where(commit_id: commit.id).order(:created_at)
present paginate(notes), with: Entities::CommitNote
end
@@ -161,7 +157,7 @@ module API
optional :path, type: String, desc: 'The file path'
given :path do
requires :line, type: Integer, desc: 'The line number'
- requires :line_type, type: String, values: ['new', 'old'], default: 'new', desc: 'The type of the line'
+ requires :line_type, type: String, values: %w(new old), default: 'new', desc: 'The type of the line'
end
end
post ':id/repository/commits/:sha/comments' do
diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb
index 3f5183d46a2..69e85c27a65 100644
--- a/lib/api/deploy_keys.rb
+++ b/lib/api/deploy_keys.rb
@@ -1,12 +1,17 @@
module API
class DeployKeys < Grape::API
+ include PaginationParams
+
before { authenticate! }
+ desc 'Return all deploy keys'
+ params do
+ use :pagination
+ end
get "deploy_keys" do
authenticated_as_admin!
- keys = DeployKey.all
- present keys, with: Entities::SSHKey
+ present paginate(DeployKey.all), with: Entities::SSHKey
end
params do
@@ -18,8 +23,11 @@ module API
desc "Get a specific project's deploy keys" do
success Entities::SSHKey
end
+ params do
+ use :pagination
+ end
get ":id/deploy_keys" do
- present user_project.deploy_keys, with: Entities::SSHKey
+ present paginate(user_project.deploy_keys), with: Entities::SSHKey
end
desc 'Get single deploy key' do
@@ -85,20 +93,6 @@ module API
end
end
- desc 'Disable a deploy key for a project' do
- detail 'This feature was added in GitLab 8.11'
- success Entities::SSHKey
- end
- params do
- requires :key_id, type: Integer, desc: 'The ID of the deploy key'
- end
- delete ":id/deploy_keys/:key_id/disable" do
- key = user_project.deploy_keys_projects.find_by(deploy_key_id: params[:key_id])
- key.destroy
-
- present key.deploy_key, with: Entities::SSHKey
- end
-
desc 'Delete deploy key for a project' do
success Key
end
@@ -107,11 +101,9 @@ module API
end
delete ":id/deploy_keys/:key_id" do
key = user_project.deploy_keys_projects.find_by(deploy_key_id: params[:key_id])
- if key
- key.destroy
- else
- not_found!('Deploy Key')
- end
+ not_found!('Deploy Key') unless key
+
+ key.destroy
end
end
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 5d7b8e021bb..0e37f40a887 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -26,7 +26,7 @@ module API
expose :last_sign_in_at
expose :confirmed_at
expose :email
- expose :theme_id, :color_scheme_id, :projects_limit, :current_sign_in_at
+ expose :color_scheme_id, :projects_limit, :current_sign_in_at
expose :identities, using: Entities::Identity
expose :can_create_group?, as: :can_create_group
expose :can_create_project?, as: :can_create_project
@@ -138,6 +138,7 @@ module API
expose :web_url
expose :request_access_enabled
expose :full_name, :full_path
+ expose :parent_id
expose :statistics, if: :statistics do
with_options format_with: -> (value) { value.to_i } do
@@ -154,10 +155,27 @@ module API
expose :shared_projects, using: Entities::Project
end
+ class RepoCommit < Grape::Entity
+ expose :id, :short_id, :title, :created_at
+ expose :parent_ids
+ expose :safe_message, as: :message
+ expose :author_name, :author_email, :authored_date
+ expose :committer_name, :committer_email, :committed_date
+ end
+
+ class RepoCommitStats < Grape::Entity
+ expose :additions, :deletions, :total
+ end
+
+ class RepoCommitDetail < RepoCommit
+ expose :stats, using: Entities::RepoCommitStats
+ expose :status
+ end
+
class RepoBranch < Grape::Entity
expose :name
- expose :commit do |repo_branch, options|
+ expose :commit, using: Entities::RepoCommit do |repo_branch, options|
options[:project].repository.commit(repo_branch.dereferenced_target)
end
@@ -192,22 +210,6 @@ module API
end
end
- class RepoCommit < Grape::Entity
- expose :id, :short_id, :title, :author_name, :author_email, :created_at
- expose :committer_name, :committer_email
- expose :safe_message, as: :message
- end
-
- class RepoCommitStats < Grape::Entity
- expose :additions, :deletions, :total
- end
-
- class RepoCommitDetail < RepoCommit
- expose :parent_ids, :committed_date, :authored_date
- expose :stats, using: Entities::RepoCommitStats
- expose :status
- end
-
class ProjectSnippet < Grape::Entity
expose :id, :title, :file_name
expose :author, using: Entities::UserBasic
@@ -337,9 +339,6 @@ module API
expose :created_at, :updated_at
expose :system?, as: :system
expose :noteable_id, :noteable_type
- # upvote? and downvote? are deprecated, always return false
- expose(:upvote?) { |note| false }
- expose(:downvote?) { |note| false }
end
class AwardEmoji < Grape::Entity
@@ -366,7 +365,7 @@ module API
class CommitStatus < Grape::Entity
expose :id, :sha, :ref, :status, :name, :target_url, :description,
- :created_at, :started_at, :finished_at, :allow_failure
+ :created_at, :started_at, :finished_at, :allow_failure, :coverage
expose :author, using: Entities::UserBasic
end
@@ -379,9 +378,7 @@ module API
expose :author, using: Entities::UserBasic, if: ->(event, options) { event.author }
expose :author_username do |event, options|
- if event.author
- event.author.username
- end
+ event.author&.username
end
end
@@ -415,7 +412,7 @@ module API
end
class Namespace < Grape::Entity
- expose :id, :name, :path, :kind
+ expose :id, :name, :path, :kind, :full_path
end
class MemberAccess < Grape::Entity
@@ -560,6 +557,7 @@ module API
expose :default_project_visibility
expose :default_snippet_visibility
expose :default_group_visibility
+ expose :default_artifacts_expire_in
expose :domain_whitelist
expose :domain_blacklist_enabled
expose :domain_blacklist
diff --git a/lib/api/files.rb b/lib/api/files.rb
index c58472de578..500f9d3c787 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -1,12 +1,11 @@
module API
- # Projects API
class Files < Grape::API
helpers do
def commit_params(attrs)
{
file_path: attrs[:file_path],
- start_branch: attrs[:branch_name],
- target_branch: attrs[:branch_name],
+ start_branch: attrs[:branch],
+ target_branch: attrs[:branch],
commit_message: attrs[:commit_message],
file_content: attrs[:content],
file_content_encoding: attrs[:encoding],
@@ -18,13 +17,13 @@ module API
def commit_response(attrs)
{
file_path: attrs[:file_path],
- branch_name: attrs[:branch_name]
+ branch: attrs[:branch]
}
end
params :simple_file_params do
requires :file_path, type: String, desc: 'The path to new file. Ex. lib/class.rb'
- requires :branch_name, type: String, desc: 'The name of branch'
+ requires :branch, type: String, desc: 'The name of branch'
requires :commit_message, type: String, desc: 'Commit Message'
optional :author_email, type: String, desc: 'The email of the author'
optional :author_name, type: String, desc: 'The name of the author'
@@ -117,7 +116,7 @@ module API
authorize! :push_code, user_project
file_params = declared_params(include_missing: false)
- result = ::Files::DeleteService.new(user_project, current_user, commit_params(file_params)).execute
+ result = ::Files::DestroyService.new(user_project, current_user, commit_params(file_params)).execute
if result[:status] == :success
status(200)
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 7682d286866..9cffd6180ae 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -36,12 +36,15 @@ module API
optional :skip_groups, type: Array[Integer], desc: 'Array of group ids to exclude from list'
optional :all_available, type: Boolean, desc: 'Show all group that you have access to'
optional :search, type: String, desc: 'Search for a specific group'
+ optional :owned, type: Boolean, default: false, desc: 'Limit by owned by authenticated user'
optional :order_by, type: String, values: %w[name path], default: 'name', desc: 'Order by name or path'
optional :sort, type: String, values: %w[asc desc], default: 'asc', desc: 'Sort by asc (ascending) or desc (descending)'
use :pagination
end
get do
- groups = if current_user.admin
+ groups = if params[:owned]
+ current_user.owned_groups
+ elsif current_user.admin
Group.all
elsif params[:all_available]
GroupsFinder.new.execute(current_user)
@@ -56,23 +59,13 @@ module API
present_groups groups, statistics: params[:statistics] && current_user.is_admin?
end
- desc 'Get list of owned groups for authenticated user' do
- success Entities::Group
- end
- params do
- use :pagination
- use :statistics_params
- end
- get '/owned' do
- present_groups current_user.owned_groups, statistics: params[:statistics]
- end
-
desc 'Create a group. Available only for users who can create groups.' do
success Entities::Group
end
params do
requires :name, type: String, desc: 'The name of the group'
requires :path, type: String, desc: 'The path of the group'
+ optional :parent_id, type: Integer, desc: 'The parent group id for creating nested group'
use :optional_params
end
post do
@@ -125,7 +118,7 @@ module API
delete ":id" do
group = find_group!(params[:id])
authorize! :admin_group, group
- DestroyGroupService.new(group, current_user).execute
+ ::Groups::DestroyService.new(group, current_user).execute
end
desc 'Get a list of projects in this group.' do
@@ -142,6 +135,9 @@ module API
desc: 'Return projects sorted in ascending and descending order'
optional :simple, type: Boolean, default: false,
desc: 'Return only the ID, URL, name, and path of each project'
+ optional :owned, type: Boolean, default: false, desc: 'Limit by owned by authenticated user'
+ optional :starred, type: Boolean, default: false, desc: 'Limit by starred status'
+
use :pagination
end
get ":id/projects" do
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index dfab60f7fa5..72d2b320077 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -3,7 +3,7 @@ module API
include Gitlab::Utils
include Helpers::Pagination
- SUDO_HEADER = "HTTP_SUDO"
+ SUDO_HEADER = "HTTP_SUDO".freeze
SUDO_PARAM = :sudo
def declared_params(options = {})
@@ -153,29 +153,13 @@ module API
params_hash = custom_params || params
attrs = {}
keys.each do |key|
- if params_hash[key].present? or (params_hash.has_key?(key) and params_hash[key] == false)
+ if params_hash[key].present? || (params_hash.has_key?(key) && params_hash[key] == false)
attrs[key] = params_hash[key]
end
end
ActionController::Parameters.new(attrs).permit!
end
- # Checks the occurrences of datetime attributes, each attribute if present in the params hash must be in ISO 8601
- # format (YYYY-MM-DDTHH:MM:SSZ) or a Bad Request error is invoked.
- #
- # Parameters:
- # keys (required) - An array consisting of elements that must be parseable as dates from the params hash
- def datetime_attributes!(*keys)
- keys.each do |key|
- begin
- params[key] = Time.xmlschema(params[key]) if params[key].present?
- rescue ArgumentError
- message = "\"" + key.to_s + "\" must be a timestamp in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ"
- render_api_error!(message, 400)
- end
- end
- end
-
def filter_by_iid(items, iid)
items.where(iid: iid)
end
@@ -225,12 +209,20 @@ module API
render_api_error!('204 No Content', 204)
end
+ def accepted!
+ render_api_error!('202 Accepted', 202)
+ end
+
def render_validation_error!(model)
if model.errors.any?
render_api_error!(model.errors.messages || '400 Bad Request', 400)
end
end
+ def render_spam_error!
+ render_api_error!({ error: 'Spam detected' }, 400)
+ end
+
def render_api_error!(message, status)
error!({ 'message' => message }, status, header)
end
@@ -256,6 +248,14 @@ module API
# project helpers
def filter_projects(projects)
+ if params[:owned]
+ projects = projects.merge(current_user.owned_projects)
+ end
+
+ if params[:starred]
+ projects = projects.merge(current_user.starred_projects)
+ end
+
if params[:search].present?
projects = projects.search(params[:search])
end
diff --git a/lib/api/helpers/pagination.rb b/lib/api/helpers/pagination.rb
index 2199eea7e5f..0764b58fb4c 100644
--- a/lib/api/helpers/pagination.rb
+++ b/lib/api/helpers/pagination.rb
@@ -2,7 +2,7 @@ module API
module Helpers
module Pagination
def paginate(relation)
- relation.page(params[:page]).per(params[:per_page].to_i).tap do |data|
+ relation.page(params[:page]).per(params[:per_page]).tap do |data|
add_pagination_headers(data)
end
end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 90fca20d4fa..6d30c5d81b1 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -10,17 +10,9 @@ module API
args.delete(:id)
args[:milestone_title] = args.delete(:milestone)
+ args[:label_name] = args.delete(:labels)
- match_all_labels = args.delete(:match_all_labels)
- labels = args.delete(:labels)
- args[:label_name] = labels if match_all_labels
-
- issues = IssuesFinder.new(current_user, args).execute.inc_notes_with_associations
-
- # TODO: Remove in 9.0 pass `label_name: args.delete(:labels)` to IssuesFinder
- if !match_all_labels && labels.present?
- issues = issues.includes(:labels).where('labels.title' => labels.split(','))
- end
+ issues = IssuesFinder.new(current_user, args).execute
issues.reorder(args[:order_by] => args[:sort])
end
@@ -77,7 +69,7 @@ module API
get ":id/issues" do
group = find_group!(params[:id])
- issues = find_issues(group_id: group.id, state: params[:state] || 'opened', match_all_labels: true)
+ issues = find_issues(group_id: group.id, state: params[:state] || 'opened')
present paginate(issues), with: Entities::Issue, current_user: current_user
end
@@ -177,9 +169,13 @@ module API
params.delete(:updated_at)
end
+ update_params = declared_params(include_missing: false).merge(request: request, api: true)
+
issue = ::Issues::UpdateService.new(user_project,
current_user,
- declared_params(include_missing: false)).execute(issue)
+ update_params).execute(issue)
+
+ render_spam_error! if issue.spam?
if issue.valid?
present issue, with: Entities::Issue, current_user: current_user, project: user_project
diff --git a/lib/api/labels.rb b/lib/api/labels.rb
index 652786d4e3e..d2955af3f95 100644
--- a/lib/api/labels.rb
+++ b/lib/api/labels.rb
@@ -1,6 +1,7 @@
module API
- # Labels API
class Labels < Grape::API
+ include PaginationParams
+
before { authenticate! }
params do
@@ -10,8 +11,11 @@ module API
desc 'Get all labels of the project' do
success Entities::Label
end
+ params do
+ use :pagination
+ end
get ':id/labels' do
- present available_labels, with: Entities::Label, current_user: current_user, project: user_project
+ present paginate(available_labels), with: Entities::Label, current_user: current_user, project: user_project
end
desc 'Create a new label' do
diff --git a/lib/api/members.rb b/lib/api/members.rb
index d85f1f78cd6..8360c007005 100644
--- a/lib/api/members.rb
+++ b/lib/api/members.rb
@@ -56,16 +56,9 @@ module API
member = source.members.find_by(user_id: params[:user_id])
- # We need this explicit check because `source.add_user` doesn't
- # currently return the member created so it would return 201 even if
- # the member already existed...
- # The `source_type == 'group'` check is to ensure back-compatibility
- # but 409 behavior should be used for both project and group members in 9.0!
- conflict!('Member already exists') if source_type == 'group' && member
-
- unless member
- member = source.add_user(params[:user_id], params[:access_level], current_user: current_user, expires_at: params[:expires_at])
- end
+ conflict!('Member already exists') if member
+
+ member = source.add_user(params[:user_id], params[:access_level], current_user: current_user, expires_at: params[:expires_at])
if member.persisted? && member.valid?
present member.user, with: Entities::Member, member: member
@@ -86,13 +79,12 @@ module API
optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY'
end
put ":id/members/:user_id" do
- source = find_source(source_type, params[:id])
+ source = find_source(source_type, params.delete(:id))
authorize_admin_source!(source_type, source)
- member = source.members.find_by!(user_id: params[:user_id])
- attrs = attributes_for_keys [:access_level, :expires_at]
+ member = source.members.find_by!(user_id: params.delete(:user_id))
- if member.update_attributes(attrs)
+ if member.update_attributes(declared_params(include_missing: false))
present member.user, with: Entities::Member, member: member
else
# This is to ensure back-compatibility but 400 behavior should be used
diff --git a/lib/api/merge_request_diffs.rb b/lib/api/merge_request_diffs.rb
index bc3d69f6904..4901a7cfea6 100644
--- a/lib/api/merge_request_diffs.rb
+++ b/lib/api/merge_request_diffs.rb
@@ -1,6 +1,8 @@
module API
# MergeRequestDiff API
class MergeRequestDiffs < Grape::API
+ include PaginationParams
+
before { authenticate! }
resource :projects do
@@ -12,12 +14,12 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
requires :merge_request_id, type: Integer, desc: 'The ID of a merge request'
+ use :pagination
end
-
get ":id/merge_requests/:merge_request_id/versions" do
merge_request = find_merge_request_with_access(params[:merge_request_id])
- present merge_request.merge_request_diffs, with: Entities::MergeRequestDiff
+ present paginate(merge_request.merge_request_diffs), with: Entities::MergeRequestDiff
end
desc 'Get a single merge request diff version' do
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 782147883c8..bdd764abfeb 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -119,8 +119,9 @@ module API
end
get ':id/merge_requests/:merge_request_id/commits' do
merge_request = find_merge_request_with_access(params[:merge_request_id])
+ commits = ::Kaminari.paginate_array(merge_request.commits)
- present merge_request.commits, with: Entities::RepoCommit
+ present paginate(commits), with: Entities::RepoCommit
end
desc 'Show the merge request changes' do
@@ -138,7 +139,7 @@ module API
params do
optional :title, type: String, allow_blank: false, desc: 'The title of the merge request'
optional :target_branch, type: String, allow_blank: false, desc: 'The target branch'
- optional :state_event, type: String, values: %w[close reopen merge],
+ optional :state_event, type: String, values: %w[close reopen],
desc: 'Status of the merge request'
use :optional_params
at_least_one_of :title, :target_branch, :description, :assignee_id,
diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb
index 3c373a84ec5..0b4ed76b35c 100644
--- a/lib/api/milestones.rb
+++ b/lib/api/milestones.rb
@@ -120,6 +120,28 @@ module API
issues = IssuesFinder.new(current_user, finder_params).execute
present paginate(issues), with: Entities::Issue, current_user: current_user, project: user_project
end
+
+ desc 'Get all merge requests for a single project milestone' do
+ detail 'This feature was introduced in GitLab 9.'
+ success Entities::MergeRequest
+ end
+ params do
+ requires :milestone_id, type: Integer, desc: 'The ID of a project milestone'
+ use :pagination
+ end
+ get ':id/milestones/:milestone_id/merge_requests' do
+ authorize! :read_milestone, user_project
+
+ milestone = user_project.milestones.find(params[:milestone_id])
+
+ finder_params = {
+ project_id: user_project.id,
+ milestone_id: milestone.id
+ }
+
+ merge_requests = MergeRequestsFinder.new(current_user, finder_params).execute
+ present paginate(merge_requests), with: Entities::MergeRequest, current_user: current_user, project: user_project
+ end
end
end
end
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 4d2a8f48267..f559a7f74a0 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -4,7 +4,7 @@ module API
before { authenticate! }
- NOTEABLE_TYPES = [Issue, MergeRequest, Snippet]
+ NOTEABLE_TYPES = [Issue, MergeRequest, Snippet].freeze
params do
requires :id, type: String, desc: 'The ID of a project'
@@ -85,7 +85,7 @@ module API
note = ::Notes::CreateService.new(user_project, current_user, opts).execute
if note.valid?
- present note, with: Entities::const_get(note.class.name)
+ present note, with: Entities.const_get(note.class.name)
else
not_found!("Note #{note.errors.messages}")
end
@@ -131,7 +131,7 @@ module API
note = user_project.notes.find(params[:note_id])
authorize! :admin_note, note
- ::Notes::DeleteService.new(user_project, current_user).execute(note)
+ ::Notes::DestroyService.new(user_project, current_user).execute(note)
present note, with: Entities::Note
end
diff --git a/lib/api/pagination_params.rb b/lib/api/pagination_params.rb
index 8c1e4381a74..f566eb3ed2b 100644
--- a/lib/api/pagination_params.rb
+++ b/lib/api/pagination_params.rb
@@ -15,8 +15,8 @@ module API
included do
helpers do
params :pagination do
- optional :page, type: Integer, desc: 'Current page number'
- optional :per_page, type: Integer, desc: 'Number of items per page'
+ optional :page, type: Integer, default: 1, desc: 'Current page number'
+ optional :per_page, type: Integer, default: 20, desc: 'Number of items per page'
end
end
end
diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb
index b634b1d0222..3afc1e385fe 100644
--- a/lib/api/pipelines.rb
+++ b/lib/api/pipelines.rb
@@ -14,7 +14,7 @@ module API
end
params do
use :pagination
- optional :scope, type: String, values: ['running', 'branches', 'tags'],
+ optional :scope, type: String, values: %w(running branches tags),
desc: 'Either running, branches, or tags'
end
get ':id/pipelines' do
@@ -23,7 +23,7 @@ module API
pipelines = PipelinesFinder.new(user_project).execute(scope: params[:scope])
present paginate(pipelines), with: Entities::Pipeline
end
-
+
desc 'Create a new pipeline' do
detail 'This feature was introduced in GitLab 8.14'
success Entities::Pipeline
@@ -58,7 +58,7 @@ module API
present pipeline, with: Entities::Pipeline
end
- desc 'Retry failed builds in the pipeline' do
+ desc 'Retry builds in the pipeline' do
detail 'This feature was introduced in GitLab 8.11.'
success Entities::Pipeline
end
diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb
index cb679e6658a..f7a28d7ad10 100644
--- a/lib/api/project_hooks.rb
+++ b/lib/api/project_hooks.rb
@@ -32,9 +32,7 @@ module API
use :pagination
end
get ":id/hooks" do
- hooks = paginate user_project.hooks
-
- present hooks, with: Entities::ProjectHook
+ present paginate(user_project.hooks), with: Entities::ProjectHook
end
desc 'Get a project hook' do
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index dcc0c82ee27..2a1cce73f3f 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -63,6 +63,8 @@ module API
snippet = CreateSnippetService.new(user_project, current_user, snippet_params).execute
+ render_spam_error! if snippet.spam?
+
if snippet.persisted?
present snippet, with: Entities::ProjectSnippet
else
@@ -92,12 +94,16 @@ module API
authorize! :update_project_snippet, snippet
snippet_params = declared_params(include_missing: false)
+ .merge(request: request, api: true)
+
snippet_params[:content] = snippet_params.delete(:code) if snippet_params[:code].present?
UpdateSnippetService.new(user_project, current_user, snippet,
snippet_params).execute
- if snippet.persisted?
+ render_spam_error! if snippet.spam?
+
+ if snippet.valid?
present snippet, with: Entities::ProjectSnippet
else
render_validation_error!(snippet)
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 92a70faf1c2..b89bddc7e29 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -16,26 +16,16 @@ module API
optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project'
optional :container_registry_enabled, type: Boolean, desc: 'Flag indication if the container registry is enabled for that project'
optional :lfs_enabled, type: Boolean, desc: 'Flag indication if Git LFS is enabled for that project'
- optional :public, type: Boolean, desc: 'Create a public project. The same as visibility_level = 20.'
optional :visibility_level, type: Integer, values: [
Gitlab::VisibilityLevel::PRIVATE,
Gitlab::VisibilityLevel::INTERNAL,
- Gitlab::VisibilityLevel::PUBLIC ], desc: 'Create a public project. The same as visibility_level = 20.'
+ Gitlab::VisibilityLevel::PUBLIC
+ ], desc: 'Create a public project. The same as visibility_level = 20.'
optional :public_builds, type: Boolean, desc: 'Perform public builds'
optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access'
optional :only_allow_merge_if_build_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed'
optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved'
end
-
- def map_public_to_visibility_level(attrs)
- publik = attrs.delete(:public)
- if !publik.nil? && !attrs[:visibility_level].present?
- # Since setting the public attribute to private could mean either
- # private or internal, use the more conservative option, private.
- attrs[:visibility_level] = (publik == true) ? Gitlab::VisibilityLevel::PUBLIC : Gitlab::VisibilityLevel::PRIVATE
- end
- attrs
- end
end
resource :projects do
@@ -61,6 +51,8 @@ module API
optional :visibility, type: String, values: %w[public internal private],
desc: 'Limit by visibility'
optional :search, type: String, desc: 'Return list of authorized projects matching the search criteria'
+ optional :owned, type: Boolean, default: false, desc: 'Limit by owned by authenticated user'
+ optional :starred, type: Boolean, default: false, desc: 'Limit by starred status'
end
params :statistics_params do
@@ -93,62 +85,9 @@ module API
params do
use :collection_params
end
- get '/visible' do
- entity = current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails
- present_projects ProjectsFinder.new.execute(current_user), with: entity
- end
-
- desc 'Get a projects list for authenticated user' do
- success Entities::BasicProjectDetails
- end
- params do
- use :collection_params
- end
get do
- authenticate!
-
- present_projects current_user.authorized_projects,
- with: Entities::ProjectWithAccess
- end
-
- desc 'Get an owned projects list for authenticated user' do
- success Entities::BasicProjectDetails
- end
- params do
- use :collection_params
- use :statistics_params
- end
- get '/owned' do
- authenticate!
-
- present_projects current_user.owned_projects,
- with: Entities::ProjectWithAccess,
- statistics: params[:statistics]
- end
-
- desc 'Gets starred project for the authenticated user' do
- success Entities::BasicProjectDetails
- end
- params do
- use :collection_params
- end
- get '/starred' do
- authenticate!
-
- present_projects current_user.viewable_starred_projects
- end
-
- desc 'Get all projects for admin user' do
- success Entities::BasicProjectDetails
- end
- params do
- use :collection_params
- use :statistics_params
- end
- get '/all' do
- authenticated_as_admin!
-
- present_projects Project.all, with: Entities::ProjectWithAccess, statistics: params[:statistics]
+ entity = current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails
+ present_projects ProjectsFinder.new.execute(current_user), with: entity, statistics: params[:statistics]
end
desc 'Create new project' do
@@ -161,7 +100,7 @@ module API
use :create_params
end
post do
- attrs = map_public_to_visibility_level(declared_params(include_missing: false))
+ attrs = declared_params(include_missing: false)
project = ::Projects::CreateService.new(current_user, attrs).execute
if project.saved?
@@ -190,7 +129,7 @@ module API
user = User.find_by(id: params.delete(:user_id))
not_found!('User') unless user
- attrs = map_public_to_visibility_level(declared_params(include_missing: false))
+ attrs = declared_params(include_missing: false)
project = ::Projects::CreateService.new(user, attrs).execute
if project.saved?
@@ -231,7 +170,7 @@ module API
params do
optional :namespace, type: String, desc: 'The ID or name of the namespace that the project will be forked into'
end
- post 'fork/:id' do
+ post ':id/fork' do
fork_params = declared_params(include_missing: false)
namespace_id = fork_params[:namespace]
@@ -268,14 +207,14 @@ module API
at_least_one_of :name, :description, :issues_enabled, :merge_requests_enabled,
:wiki_enabled, :builds_enabled, :snippets_enabled,
:shared_runners_enabled, :container_registry_enabled,
- :lfs_enabled, :public, :visibility_level, :public_builds,
+ :lfs_enabled, :visibility_level, :public_builds,
:request_access_enabled, :only_allow_merge_if_build_succeeds,
:only_allow_merge_if_all_discussions_are_resolved, :path,
:default_branch
end
put ':id' do
authorize_admin_project
- attrs = map_public_to_visibility_level(declared_params(include_missing: false))
+ attrs = declared_params(include_missing: false)
authorize! :rename_project, user_project if attrs[:name].present?
authorize! :change_visibility_level, user_project if attrs[:visibility_level].present?
@@ -328,7 +267,7 @@ module API
desc 'Unstar a project' do
success Entities::Project
end
- delete ':id/star' do
+ post ':id/unstar' do
if current_user.starred?(user_project)
current_user.toggle_star(user_project)
user_project.reload
@@ -343,6 +282,8 @@ module API
delete ":id" do
authorize! :remove_project, user_project
::Projects::DestroyService.new(user_project, current_user, {}).async_execute
+
+ accepted!
end
desc 'Mark this project as forked from another'
@@ -436,6 +377,19 @@ module API
present paginate(users), with: Entities::UserBasic
end
+
+ desc 'Start the housekeeping task for a project' do
+ detail 'This feature was introduced in GitLab 9.0.'
+ end
+ post ':id/housekeeping' do
+ authorize_admin_project
+
+ begin
+ ::Projects::HousekeepingService.new(user_project).execute
+ rescue ::Projects::HousekeepingService::LeaseTaken => error
+ conflict!(error.message)
+ end
+ end
end
end
end
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index 4ca6646a6f1..36166780149 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -2,6 +2,8 @@ require 'mime/types'
module API
class Repositories < Grape::API
+ include PaginationParams
+
before { authorize! :download_code, user_project }
params do
@@ -24,6 +26,7 @@ module API
optional :ref_name, type: String, desc: 'The name of a repository branch or tag, if not given the default branch is used'
optional :path, type: String, desc: 'The path of the tree'
optional :recursive, type: Boolean, default: false, desc: 'Used to get a recursive tree'
+ use :pagination
end
get ':id/repository/tree' do
ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
@@ -33,8 +36,8 @@ module API
not_found!('Tree') unless commit
tree = user_project.repository.tree(commit.id, path, recursive: params[:recursive])
-
- present tree.sorted_entries, with: Entities::RepoTreeObject
+ entries = ::Kaminari.paginate_array(tree.sorted_entries)
+ present paginate(entries), with: Entities::RepoTreeObject
end
desc 'Get a raw file contents'
@@ -42,7 +45,7 @@ module API
requires :sha, type: String, desc: 'The commit, branch name, or tag name'
requires :filepath, type: String, desc: 'The path to the file to display'
end
- get [ ":id/repository/blobs/:sha", ":id/repository/commits/:sha/blob" ] do
+ get [":id/repository/blobs/:sha", ":id/repository/commits/:sha/blob"] do
repo = user_project.repository
commit = repo.commit(params[:sha])
@@ -100,10 +103,13 @@ module API
desc 'Get repository contributors' do
success Entities::Contributor
end
+ params do
+ use :pagination
+ end
get ':id/repository/contributors' do
begin
- present user_project.repository.contributors,
- with: Entities::Contributor
+ contributors = ::Kaminari.paginate_array(user_project.repository.contributors)
+ present paginate(contributors), with: Entities::Contributor
rescue
not_found!
end
diff --git a/lib/api/runners.rb b/lib/api/runners.rb
index 4816b5ed1b7..252e59bfa58 100644
--- a/lib/api/runners.rb
+++ b/lib/api/runners.rb
@@ -14,7 +14,7 @@ module API
use :pagination
end
get do
- runners = filter_runners(current_user.ci_authorized_runners, params[:scope], without: ['specific', 'shared'])
+ runners = filter_runners(current_user.ci_authorized_runners, params[:scope], without: %w(specific shared))
present paginate(runners), with: Entities::Runner
end
@@ -60,8 +60,9 @@ module API
put ':id' do
runner = get_runner(params.delete(:id))
authenticate_update_runner!(runner)
+ update_service = Ci::UpdateRunnerService.new(runner)
- if runner.update(declared_params(include_missing: false))
+ if update_service.update(declared_params(include_missing: false))
present runner, with: Entities::RunnerDetails, current_user: current_user
else
render_validation_error!(runner)
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index 747ceb4e3e0..936c7e0930b 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -56,7 +56,8 @@ module API
given shared_runners_enabled: ->(val) { val } do
requires :shared_runners_text, type: String, desc: 'Shared runners text '
end
- optional :max_artifacts_size, type: Integer, desc: "Set the maximum file size each build's artifacts can have"
+ optional :max_artifacts_size, type: Integer, desc: "Set the maximum file size for each job's artifacts"
+ optional :default_artifacts_expire_in, type: String, desc: "Set the default expiration time for each job's artifacts"
optional :max_pages_size, type: Integer, desc: 'Maximum size of pages in MB'
optional :container_registry_token_expire_delay, type: Integer, desc: 'Authorization token duration (minutes)'
optional :metrics_enabled, type: Boolean, desc: 'Enable the InfluxDB metrics'
@@ -117,7 +118,9 @@ module API
:send_user_confirmation_email, :domain_whitelist, :domain_blacklist_enabled,
:after_sign_up_text, :signin_enabled, :require_two_factor_authentication,
:home_page_url, :after_sign_out_path, :sign_in_text, :help_page_text,
- :shared_runners_enabled, :max_artifacts_size, :max_pages_size, :container_registry_token_expire_delay,
+ :shared_runners_enabled, :max_artifacts_size,
+ :default_artifacts_expire_in, :max_pages_size,
+ :container_registry_token_expire_delay,
:metrics_enabled, :sidekiq_throttling_enabled, :recaptcha_enabled,
:akismet_enabled, :admin_notification_email, :sentry_enabled,
:repository_storage, :repository_checks_enabled, :koding_enabled, :plantuml_enabled,
diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb
index eb9ece49e7f..ac03fbd2a3d 100644
--- a/lib/api/snippets.rb
+++ b/lib/api/snippets.rb
@@ -67,6 +67,8 @@ module API
attrs = declared_params(include_missing: false).merge(request: request, api: true)
snippet = CreateSnippetService.new(nil, current_user, attrs).execute
+ render_spam_error! if snippet.spam?
+
if snippet.persisted?
present snippet, with: Entities::PersonalSnippet
else
@@ -93,9 +95,12 @@ module API
return not_found!('Snippet') unless snippet
authorize! :update_personal_snippet, snippet
- attrs = declared_params(include_missing: false)
+ attrs = declared_params(include_missing: false).merge(request: request, api: true)
UpdateSnippetService.new(nil, current_user, snippet, attrs).execute
+
+ render_spam_error! if snippet.spam?
+
if snippet.persisted?
present snippet, with: Entities::PersonalSnippet
else
diff --git a/lib/api/subscriptions.rb b/lib/api/subscriptions.rb
index e11d7537cc9..772b5cca017 100644
--- a/lib/api/subscriptions.rb
+++ b/lib/api/subscriptions.rb
@@ -3,7 +3,6 @@ module API
before { authenticate! }
subscribable_types = {
- 'merge_request' => proc { |id| find_merge_request_with_access(id, :update_merge_request) },
'merge_requests' => proc { |id| find_merge_request_with_access(id, :update_merge_request) },
'issues' => proc { |id| find_project_issue(id) },
'labels' => proc { |id| find_project_label(id) },
@@ -21,7 +20,7 @@ module API
desc 'Subscribe to a resource' do
success entity_class
end
- post ":id/#{type}/:subscribable_id/subscription" do
+ post ":id/#{type}/:subscribable_id/subscribe" do
resource = instance_exec(params[:subscribable_id], &finder)
if resource.subscribed?(current_user, user_project)
@@ -35,7 +34,7 @@ module API
desc 'Unsubscribe from a resource' do
success entity_class
end
- delete ":id/#{type}/:subscribable_id/subscription" do
+ post ":id/#{type}/:subscribable_id/unsubscribe" do
resource = instance_exec(params[:subscribable_id], &finder)
if !resource.subscribed?(current_user, user_project)
diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb
index 708ec8cfe70..d038a3fa828 100644
--- a/lib/api/system_hooks.rb
+++ b/lib/api/system_hooks.rb
@@ -1,6 +1,7 @@
module API
- # Hooks API
class SystemHooks < Grape::API
+ include PaginationParams
+
before do
authenticate!
authenticated_as_admin!
@@ -10,10 +11,11 @@ module API
desc 'Get the list of system hooks' do
success Entities::Hook
end
+ params do
+ use :pagination
+ end
get do
- hooks = SystemHook.all
-
- present hooks, with: Entities::Hook
+ present paginate(SystemHook.all), with: Entities::Hook
end
desc 'Create a new system hook' do
diff --git a/lib/api/tags.rb b/lib/api/tags.rb
index 5b345db3a41..86759ab882f 100644
--- a/lib/api/tags.rb
+++ b/lib/api/tags.rb
@@ -1,6 +1,7 @@
module API
- # Git Tags API
class Tags < Grape::API
+ include PaginationParams
+
before { authorize! :download_code, user_project }
params do
@@ -10,9 +11,12 @@ module API
desc 'Get a project repository tags' do
success Entities::RepoTag
end
+ params do
+ use :pagination
+ end
get ":id/repository/tags" do
- present user_project.repository.tags.sort_by(&:name).reverse,
- with: Entities::RepoTag, project: user_project
+ tags = ::Kaminari.paginate_array(user_project.repository.tags.sort_by(&:name).reverse)
+ present paginate(tags), with: Entities::RepoTag, project: user_project
end
desc 'Get a single repository tag' do
@@ -40,7 +44,7 @@ module API
post ':id/repository/tags' do
authorize_push_project
- result = CreateTagService.new(user_project, current_user).
+ result = ::Tags::CreateService.new(user_project, current_user).
execute(params[:tag_name], params[:ref], params[:message], params[:release_description])
if result[:status] == :success
@@ -59,7 +63,7 @@ module API
delete ":id/repository/tags/:tag_name", requirements: { tag_name: /.+/ } do
authorize_push_project
- result = DeleteTagService.new(user_project, current_user).
+ result = ::Tags::DestroyService.new(user_project, current_user).
execute(params[:tag_name])
if result[:status] == :success
diff --git a/lib/api/templates.rb b/lib/api/templates.rb
index e23f99256a5..0fc13b35d5b 100644
--- a/lib/api/templates.rb
+++ b/lib/api/templates.rb
@@ -1,5 +1,7 @@
module API
class Templates < Grape::API
+ include PaginationParams
+
GLOBAL_TEMPLATE_TYPES = {
gitignores: {
klass: Gitlab::Template::GitignoreTemplate,
@@ -24,7 +26,6 @@ module API
/[\<\{\[]
(fullname|name\sof\s(author|copyright\sowner))
[\>\}\]]/xi.freeze
- DEPRECATION_MESSAGE = ' This endpoint is deprecated and will be removed in GitLab 9.0.'.freeze
helpers do
def parsed_license_template
@@ -46,74 +47,64 @@ module API
end
end
- { "licenses" => :deprecated, "templates/licenses" => :ok }.each do |route, status|
- desc 'Get the list of the available license template' do
- detailed_desc = 'This feature was introduced in GitLab 8.7.'
- detailed_desc << DEPRECATION_MESSAGE unless status == :ok
- detail detailed_desc
- success Entities::RepoLicense
- end
- params do
- optional :popular, type: Boolean, desc: 'If passed, returns only popular licenses'
- end
- get route do
- options = {
- featured: declared(params).popular.present? ? true : nil
- }
- present Licensee::License.all(options), with: Entities::RepoLicense
- end
+ desc 'Get the list of the available license template' do
+ detail 'This feature was introduced in GitLab 8.7.'
+ success ::API::Entities::RepoLicense
+ end
+ params do
+ optional :popular, type: Boolean, desc: 'If passed, returns only popular licenses'
+ use :pagination
+ end
+ get "templates/licenses" do
+ options = {
+ featured: declared(params).popular.present? ? true : nil
+ }
+ licences = ::Kaminari.paginate_array(Licensee::License.all(options))
+ present paginate(licences), with: Entities::RepoLicense
end
- { "licenses/:name" => :deprecated, "templates/licenses/:name" => :ok }.each do |route, status|
- desc 'Get the text for a specific license' do
- detailed_desc = 'This feature was introduced in GitLab 8.7.'
- detailed_desc << DEPRECATION_MESSAGE unless status == :ok
- detail detailed_desc
- success Entities::RepoLicense
- end
- params do
- requires :name, type: String, desc: 'The name of the template'
- end
- get route, requirements: { name: /[\w\.-]+/ } do
- not_found!('License') unless Licensee::License.find(declared(params).name)
+ desc 'Get the text for a specific license' do
+ detail 'This feature was introduced in GitLab 8.7.'
+ success ::API::Entities::RepoLicense
+ end
+ params do
+ requires :name, type: String, desc: 'The name of the template'
+ end
+ get "templates/licenses/:name", requirements: { name: /[\w\.-]+/ } do
+ not_found!('License') unless Licensee::License.find(declared(params).name)
- template = parsed_license_template
+ template = parsed_license_template
- present template, with: Entities::RepoLicense
- end
+ present template, with: ::API::Entities::RepoLicense
end
GLOBAL_TEMPLATE_TYPES.each do |template_type, properties|
klass = properties[:klass]
gitlab_version = properties[:gitlab_version]
- { template_type => :deprecated, "templates/#{template_type}" => :ok }.each do |route, status|
- desc 'Get the list of the available template' do
- detailed_desc = "This feature was introduced in GitLab #{gitlab_version}."
- detailed_desc << DEPRECATION_MESSAGE unless status == :ok
- detail detailed_desc
- success Entities::TemplatesList
- end
- get route do
- present klass.all, with: Entities::TemplatesList
- end
+ desc 'Get the list of the available template' do
+ detail "This feature was introduced in GitLab #{gitlab_version}."
+ success Entities::TemplatesList
+ end
+ params do
+ use :pagination
+ end
+ get "templates/#{template_type}" do
+ templates = ::Kaminari.paginate_array(klass.all)
+ present paginate(templates), with: Entities::TemplatesList
end
- { "#{template_type}/:name" => :deprecated, "templates/#{template_type}/:name" => :ok }.each do |route, status|
- desc 'Get the text for a specific template present in local filesystem' do
- detailed_desc = "This feature was introduced in GitLab #{gitlab_version}."
- detailed_desc << DEPRECATION_MESSAGE unless status == :ok
- detail detailed_desc
- success Entities::Template
- end
- params do
- requires :name, type: String, desc: 'The name of the template'
- end
- get route do
- new_template = klass.find(declared(params).name)
+ desc 'Get the text for a specific template present in local filesystem' do
+ detail "This feature was introduced in GitLab #{gitlab_version}."
+ success Entities::Template
+ end
+ params do
+ requires :name, type: String, desc: 'The name of the template'
+ end
+ get "templates/#{template_type}/:name" do
+ new_template = klass.find(declared(params).name)
- render_response(template_type, new_template)
- end
+ render_response(template_type, new_template)
end
end
end
diff --git a/lib/api/todos.rb b/lib/api/todos.rb
index 9bd077263a7..e59030428da 100644
--- a/lib/api/todos.rb
+++ b/lib/api/todos.rb
@@ -7,7 +7,7 @@ module API
ISSUABLE_TYPES = {
'merge_requests' => ->(id) { find_merge_request_with_access(id) },
'issues' => ->(id) { find_project_issue(id) }
- }
+ }.freeze
params do
requires :id, type: String, desc: 'The ID of a project'
@@ -58,7 +58,7 @@ module API
params do
requires :id, type: Integer, desc: 'The ID of the todo being marked as done'
end
- delete ':id' do
+ post ':id/mark_as_done' do
todo = current_user.todos.find(params[:id])
TodoService.new.mark_todos_as_done([todo], current_user)
@@ -66,9 +66,11 @@ module API
end
desc 'Mark all todos as done'
- delete do
+ post '/mark_as_done' do
todos = find_todos
TodoService.new.mark_todos_as_done(todos, current_user)
+
+ no_content!
end
end
end
diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb
index 87a717ba751..ea0ad852633 100644
--- a/lib/api/triggers.rb
+++ b/lib/api/triggers.rb
@@ -21,14 +21,9 @@ module API
unauthorized! unless trigger.project == project
# validate variables
- variables = params[:variables]
- if variables
- unless variables.all? { |key, value| key.is_a?(String) && value.is_a?(String) }
- render_api_error!('variables needs to be a map of key-valued strings', 400)
- end
-
- # convert variables from Mash to Hash
- variables = variables.to_h
+ variables = params[:variables].to_h
+ unless variables.all? { |key, value| key.is_a?(String) && value.is_a?(String) }
+ render_api_error!('variables needs to be a map of key-valued strings', 400)
end
# create request and trigger builds
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 0ed468626b7..94b2b6653d2 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -82,7 +82,9 @@ module API
end
params do
requires :email, type: String, desc: 'The email of the user'
- requires :password, type: String, desc: 'The password of the new user'
+ optional :password, type: String, desc: 'The password of the new user'
+ optional :reset_password, type: Boolean, desc: 'Flag indicating the user will be sent a password reset token'
+ at_least_one_of :password, :reset_password
requires :name, type: String, desc: 'The name of the user'
requires :username, type: String, desc: 'The username of the user'
use :optional_attributes
@@ -94,8 +96,18 @@ module API
user_params = declared_params(include_missing: false)
identity_attrs = user_params.slice(:provider, :extern_uid)
confirm = user_params.delete(:confirm)
+ user = User.new(user_params.except(:extern_uid, :provider, :reset_password))
+
+ if user_params.delete(:reset_password)
+ user.attributes = {
+ force_random_password: true,
+ password_expires_at: nil,
+ created_by_id: current_user.id
+ }
+ user.generate_password
+ user.generate_reset_token
+ end
- user = User.new(user_params.except(:extern_uid, :provider))
user.skip_confirmation! unless confirm
if identity_attrs.any?
@@ -160,7 +172,7 @@ module API
end
end
- user_params.merge!(password_expires_at: Time.now) if user_params[:password].present?
+ user_params[:password_expires_at] = Time.now if user_params[:password].present?
if user.update_attributes(user_params.except(:extern_uid, :provider))
present user, with: Entities::UserPublic
@@ -197,6 +209,7 @@ module API
end
params do
requires :id, type: Integer, desc: 'The ID of the user'
+ use :pagination
end
get ':id/keys' do
authenticated_as_admin!
@@ -204,7 +217,7 @@ module API
user = User.find_by(id: params[:id])
not_found!('User') unless user
- present user.keys, with: Entities::SSHKey
+ present paginate(user.keys), with: Entities::SSHKey
end
desc 'Delete an existing SSH key from a specified user. Available only for admins.' do
@@ -254,13 +267,14 @@ module API
end
params do
requires :id, type: Integer, desc: 'The ID of the user'
+ use :pagination
end
get ':id/emails' do
authenticated_as_admin!
user = User.find_by(id: params[:id])
not_found!('User') unless user
- present user.emails, with: Entities::Email
+ present paginate(user.emails), with: Entities::Email
end
desc 'Delete an email address of a specified user. Available only for admins.' do
@@ -293,14 +307,14 @@ module API
user = User.find_by(id: params[:id])
not_found!('User') unless user
- DeleteUserService.new(current_user).execute(user)
+ ::Users::DestroyService.new(current_user).execute(user)
end
desc 'Block a user. Available only for admins.'
params do
requires :id, type: Integer, desc: 'The ID of the user'
end
- put ':id/block' do
+ post ':id/block' do
authenticated_as_admin!
user = User.find_by(id: params[:id])
not_found!('User') unless user
@@ -316,7 +330,7 @@ module API
params do
requires :id, type: Integer, desc: 'The ID of the user'
end
- put ':id/unblock' do
+ post ':id/unblock' do
authenticated_as_admin!
user = User.find_by(id: params[:id])
not_found!('User') unless user
@@ -361,8 +375,11 @@ module API
desc "Get the currently authenticated user's SSH keys" do
success Entities::SSHKey
end
+ params do
+ use :pagination
+ end
get "keys" do
- present current_user.keys, with: Entities::SSHKey
+ present paginate(current_user.keys), with: Entities::SSHKey
end
desc 'Get a single key owned by currently authenticated user' do
@@ -411,8 +428,11 @@ module API
desc "Get the currently authenticated user's email addresses" do
success Entities::Email
end
+ params do
+ use :pagination
+ end
get "emails" do
- present current_user.emails, with: Entities::Email
+ present paginate(current_user.emails), with: Entities::Email
end
desc 'Get a single email address owned by the currently authenticated user' do
diff --git a/lib/api/v3/boards.rb b/lib/api/v3/boards.rb
new file mode 100644
index 00000000000..31d708bc2c8
--- /dev/null
+++ b/lib/api/v3/boards.rb
@@ -0,0 +1,51 @@
+module API
+ module V3
+ class Boards < Grape::API
+ before { authenticate! }
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects do
+ desc 'Get all project boards' do
+ detail 'This feature was introduced in 8.13'
+ success ::API::Entities::Board
+ end
+ get ':id/boards' do
+ authorize!(:read_board, user_project)
+ present user_project.boards, with: ::API::Entities::Board
+ end
+
+ params do
+ requires :board_id, type: Integer, desc: 'The ID of a board'
+ end
+ segment ':id/boards/:board_id' do
+ helpers do
+ def project_board
+ board = user_project.boards.first
+
+ if params[:board_id] == board.id
+ board
+ else
+ not_found!('Board')
+ end
+ end
+
+ def board_lists
+ project_board.lists.destroyable
+ end
+ end
+
+ desc 'Get the lists of a project board' do
+ detail 'Does not include `done` list. This feature was introduced in 8.13'
+ success ::API::Entities::List
+ end
+ get '/lists' do
+ authorize!(:read_board, user_project)
+ present board_lists, with: ::API::Entities::List
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/branches.rb b/lib/api/v3/branches.rb
new file mode 100644
index 00000000000..51eb566cf7d
--- /dev/null
+++ b/lib/api/v3/branches.rb
@@ -0,0 +1,31 @@
+require 'mime/types'
+
+module API
+ module V3
+ class Branches < Grape::API
+ before { authenticate! }
+ before { authorize! :download_code, user_project }
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects do
+ desc 'Get a project repository branches' do
+ success ::API::Entities::RepoBranch
+ end
+ get ":id/repository/branches" do
+ branches = user_project.repository.branches.sort_by(&:name)
+
+ present branches, with: ::API::Entities::RepoBranch, project: user_project
+ end
+
+ desc 'Delete all merged branches'
+ delete ":id/repository/merged_branches" do
+ DeleteMergedBranchesService.new(user_project, current_user).async_execute
+
+ status(200)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/commits.rb b/lib/api/v3/commits.rb
new file mode 100644
index 00000000000..126cc95fc3d
--- /dev/null
+++ b/lib/api/v3/commits.rb
@@ -0,0 +1,205 @@
+require 'mime/types'
+
+module API
+ module V3
+ class Commits < Grape::API
+ include PaginationParams
+
+ before { authenticate! }
+ before { authorize! :download_code, user_project }
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects do
+ desc 'Get a project repository commits' do
+ success ::API::Entities::RepoCommit
+ end
+ params do
+ optional :ref_name, type: String, desc: 'The name of a repository branch or tag, if not given the default branch is used'
+ optional :since, type: DateTime, desc: 'Only commits after or in this date will be returned'
+ optional :until, type: DateTime, desc: 'Only commits before or in this date will be returned'
+ optional :page, type: Integer, default: 0, desc: 'The page for pagination'
+ optional :per_page, type: Integer, default: 20, desc: 'The number of results per page'
+ optional :path, type: String, desc: 'The file path'
+ end
+ get ":id/repository/commits" do
+ ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
+ offset = params[:page] * params[:per_page]
+
+ commits = user_project.repository.commits(ref,
+ path: params[:path],
+ limit: params[:per_page],
+ offset: offset,
+ after: params[:since],
+ before: params[:until])
+
+ present commits, with: ::API::Entities::RepoCommit
+ end
+
+ desc 'Commit multiple file changes as one commit' do
+ success ::API::Entities::RepoCommitDetail
+ detail 'This feature was introduced in GitLab 8.13'
+ end
+ params do
+ requires :branch_name, type: String, desc: 'The name of branch'
+ requires :commit_message, type: String, desc: 'Commit message'
+ requires :actions, type: Array[Hash], desc: 'Actions to perform in commit'
+ optional :author_email, type: String, desc: 'Author email for commit'
+ optional :author_name, type: String, desc: 'Author name for commit'
+ end
+ post ":id/repository/commits" do
+ authorize! :push_code, user_project
+
+ attrs = declared_params.dup
+ branch = attrs.delete(:branch_name)
+ attrs.merge!(branch: branch, start_branch: branch, target_branch: branch)
+
+ attrs[:actions].map! do |action|
+ action[:action] = action[:action].to_sym
+ action[:file_path].slice!(0) if action[:file_path] && action[:file_path].start_with?('/')
+ action[:previous_path].slice!(0) if action[:previous_path] && action[:previous_path].start_with?('/')
+ action
+ end
+
+ result = ::Files::MultiService.new(user_project, current_user, attrs).execute
+
+ if result[:status] == :success
+ commit_detail = user_project.repository.commits(result[:result], limit: 1).first
+ present commit_detail, with: ::API::Entities::RepoCommitDetail
+ else
+ render_api_error!(result[:message], 400)
+ end
+ end
+
+ desc 'Get a specific commit of a project' do
+ success ::API::Entities::RepoCommitDetail
+ failure [[404, 'Not Found']]
+ end
+ params do
+ requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag'
+ end
+ get ":id/repository/commits/:sha" do
+ commit = user_project.commit(params[:sha])
+
+ not_found! "Commit" unless commit
+
+ present commit, with: ::API::Entities::RepoCommitDetail
+ end
+
+ desc 'Get the diff for a specific commit of a project' do
+ failure [[404, 'Not Found']]
+ end
+ params do
+ requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag'
+ end
+ get ":id/repository/commits/:sha/diff" do
+ commit = user_project.commit(params[:sha])
+
+ not_found! "Commit" unless commit
+
+ commit.raw_diffs.to_a
+ end
+
+ desc "Get a commit's comments" do
+ success ::API::Entities::CommitNote
+ failure [[404, 'Not Found']]
+ end
+ params do
+ use :pagination
+ requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag'
+ end
+ get ':id/repository/commits/:sha/comments' do
+ commit = user_project.commit(params[:sha])
+
+ not_found! 'Commit' unless commit
+ notes = Note.where(commit_id: commit.id).order(:created_at)
+
+ present paginate(notes), with: ::API::Entities::CommitNote
+ end
+
+ desc 'Cherry pick commit into a branch' do
+ detail 'This feature was introduced in GitLab 8.15'
+ success ::API::Entities::RepoCommit
+ end
+ params do
+ requires :sha, type: String, desc: 'A commit sha to be cherry picked'
+ requires :branch, type: String, desc: 'The name of the branch'
+ end
+ post ':id/repository/commits/:sha/cherry_pick' do
+ authorize! :push_code, user_project
+
+ commit = user_project.commit(params[:sha])
+ not_found!('Commit') unless commit
+
+ branch = user_project.repository.find_branch(params[:branch])
+ not_found!('Branch') unless branch
+
+ commit_params = {
+ commit: commit,
+ create_merge_request: false,
+ source_project: user_project,
+ source_branch: commit.cherry_pick_branch_name,
+ target_branch: params[:branch]
+ }
+
+ result = ::Commits::CherryPickService.new(user_project, current_user, commit_params).execute
+
+ if result[:status] == :success
+ branch = user_project.repository.find_branch(params[:branch])
+ present user_project.repository.commit(branch.dereferenced_target), with: ::API::Entities::RepoCommit
+ else
+ render_api_error!(result[:message], 400)
+ end
+ end
+
+ desc 'Post comment to commit' do
+ success ::API::Entities::CommitNote
+ end
+ params do
+ requires :sha, type: String, regexp: /\A\h{6,40}\z/, desc: "The commit's SHA"
+ requires :note, type: String, desc: 'The text of the comment'
+ optional :path, type: String, desc: 'The file path'
+ given :path do
+ requires :line, type: Integer, desc: 'The line number'
+ requires :line_type, type: String, values: %w(new old), default: 'new', desc: 'The type of the line'
+ end
+ end
+ post ':id/repository/commits/:sha/comments' do
+ commit = user_project.commit(params[:sha])
+ not_found! 'Commit' unless commit
+
+ opts = {
+ note: params[:note],
+ noteable_type: 'Commit',
+ commit_id: commit.id
+ }
+
+ if params[:path]
+ commit.raw_diffs(all_diffs: true).each do |diff|
+ next unless diff.new_path == params[:path]
+ lines = Gitlab::Diff::Parser.new.parse(diff.diff.each_line)
+
+ lines.each do |line|
+ next unless line.new_pos == params[:line] && line.type == params[:line_type]
+ break opts[:line_code] = Gitlab::Diff::LineCode.generate(diff.new_path, line.new_pos, line.old_pos)
+ end
+
+ break if opts[:line_code]
+ end
+
+ opts[:type] = LegacyDiffNote.name if opts[:line_code]
+ end
+
+ note = ::Notes::CreateService.new(user_project, current_user, opts).execute
+
+ if note.save
+ present note, with: ::API::Entities::CommitNote
+ else
+ render_api_error!("Failed to save note #{note.errors.messages}", 400)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/entities.rb b/lib/api/v3/entities.rb
index 3cc0dc968a8..11d0e6dbf71 100644
--- a/lib/api/v3/entities.rb
+++ b/lib/api/v3/entities.rb
@@ -11,6 +11,40 @@ module API
Gitlab::UrlBuilder.build(snippet)
end
end
+
+ class Note < Grape::Entity
+ expose :id
+ expose :note, as: :body
+ expose :attachment_identifier, as: :attachment
+ expose :author, using: ::API::Entities::UserBasic
+ expose :created_at, :updated_at
+ expose :system?, as: :system
+ expose :noteable_id, :noteable_type
+ # upvote? and downvote? are deprecated, always return false
+ expose(:upvote?) { |note| false }
+ expose(:downvote?) { |note| false }
+ end
+
+ class Event < Grape::Entity
+ expose :title, :project_id, :action_name
+ expose :target_id, :target_type, :author_id
+ expose :data, :target_title
+ expose :created_at
+ expose :note, using: Entities::Note, if: ->(event, options) { event.note? }
+ expose :author, using: ::API::Entities::UserBasic, if: ->(event, options) { event.author }
+
+ expose :author_username do |event, options|
+ event.author&.username
+ end
+ end
+
+ class AwardEmoji < Grape::Entity
+ expose :id
+ expose :name
+ expose :user, using: ::API::Entities::UserBasic
+ expose :created_at, :updated_at
+ expose :awardable_id, :awardable_type
+ end
end
end
end
diff --git a/lib/api/v3/files.rb b/lib/api/v3/files.rb
new file mode 100644
index 00000000000..4f8d58d37c8
--- /dev/null
+++ b/lib/api/v3/files.rb
@@ -0,0 +1,138 @@
+module API
+ module V3
+ class Files < Grape::API
+ helpers do
+ def commit_params(attrs)
+ {
+ file_path: attrs[:file_path],
+ start_branch: attrs[:branch],
+ target_branch: attrs[:branch],
+ commit_message: attrs[:commit_message],
+ file_content: attrs[:content],
+ file_content_encoding: attrs[:encoding],
+ author_email: attrs[:author_email],
+ author_name: attrs[:author_name]
+ }
+ end
+
+ def commit_response(attrs)
+ {
+ file_path: attrs[:file_path],
+ branch: attrs[:branch]
+ }
+ end
+
+ params :simple_file_params do
+ requires :file_path, type: String, desc: 'The path to new file. Ex. lib/class.rb'
+ requires :branch_name, type: String, desc: 'The name of branch'
+ requires :commit_message, type: String, desc: 'Commit Message'
+ optional :author_email, type: String, desc: 'The email of the author'
+ optional :author_name, type: String, desc: 'The name of the author'
+ end
+
+ params :extended_file_params do
+ use :simple_file_params
+ requires :content, type: String, desc: 'File content'
+ optional :encoding, type: String, values: %w[base64], desc: 'File encoding'
+ end
+ end
+
+ params do
+ requires :id, type: String, desc: 'The project ID'
+ end
+ resource :projects do
+ desc 'Get a file from repository'
+ params do
+ requires :file_path, type: String, desc: 'The path to the file. Ex. lib/class.rb'
+ requires :ref, type: String, desc: 'The name of branch, tag, or commit'
+ end
+ get ":id/repository/files" do
+ authorize! :download_code, user_project
+
+ commit = user_project.commit(params[:ref])
+ not_found!('Commit') unless commit
+
+ repo = user_project.repository
+ blob = repo.blob_at(commit.sha, params[:file_path])
+ not_found!('File') unless blob
+
+ blob.load_all_data!(repo)
+ status(200)
+
+ {
+ file_name: blob.name,
+ file_path: blob.path,
+ size: blob.size,
+ encoding: "base64",
+ content: Base64.strict_encode64(blob.data),
+ ref: params[:ref],
+ blob_id: blob.id,
+ commit_id: commit.id,
+ last_commit_id: repo.last_commit_id_for_path(commit.sha, params[:file_path])
+ }
+ end
+
+ desc 'Create new file in repository'
+ params do
+ use :extended_file_params
+ end
+ post ":id/repository/files" do
+ authorize! :push_code, user_project
+
+ file_params = declared_params(include_missing: false)
+ file_params[:branch] = file_params.delete(:branch_name)
+
+ result = ::Files::CreateService.new(user_project, current_user, commit_params(file_params)).execute
+
+ if result[:status] == :success
+ status(201)
+ commit_response(file_params)
+ else
+ render_api_error!(result[:message], 400)
+ end
+ end
+
+ desc 'Update existing file in repository'
+ params do
+ use :extended_file_params
+ end
+ put ":id/repository/files" do
+ authorize! :push_code, user_project
+
+ file_params = declared_params(include_missing: false)
+ file_params[:branch] = file_params.delete(:branch_name)
+
+ result = ::Files::UpdateService.new(user_project, current_user, commit_params(file_params)).execute
+
+ if result[:status] == :success
+ status(200)
+ commit_response(file_params)
+ else
+ http_status = result[:http_status] || 400
+ render_api_error!(result[:message], http_status)
+ end
+ end
+
+ desc 'Delete an existing file in repository'
+ params do
+ use :simple_file_params
+ end
+ delete ":id/repository/files" do
+ authorize! :push_code, user_project
+
+ file_params = declared_params(include_missing: false)
+ file_params[:branch] = file_params.delete(:branch_name)
+
+ result = ::Files::DestroyService.new(user_project, current_user, commit_params(file_params)).execute
+
+ if result[:status] == :success
+ status(200)
+ commit_response(file_params)
+ else
+ render_api_error!(result[:message], 400)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/groups.rb b/lib/api/v3/groups.rb
new file mode 100644
index 00000000000..c826bc4fe0b
--- /dev/null
+++ b/lib/api/v3/groups.rb
@@ -0,0 +1,38 @@
+module API
+ module V3
+ class Groups < Grape::API
+ include PaginationParams
+
+ before { authenticate! }
+
+ helpers do
+ params :statistics_params do
+ optional :statistics, type: Boolean, default: false, desc: 'Include project statistics'
+ end
+
+ def present_groups(groups, options = {})
+ options = options.reverse_merge(
+ with: ::API::Entities::Group,
+ current_user: current_user,
+ )
+
+ groups = groups.with_statistics if options[:statistics]
+ present paginate(groups), options
+ end
+ end
+
+ resource :groups do
+ desc 'Get list of owned groups for authenticated user' do
+ success ::API::Entities::Group
+ end
+ params do
+ use :pagination
+ use :statistics_params
+ end
+ get '/owned' do
+ present_groups current_user.owned_groups, statistics: params[:statistics]
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/issues.rb b/lib/api/v3/issues.rb
index 081d45165e8..d0af09f0e1e 100644
--- a/lib/api/v3/issues.rb
+++ b/lib/api/v3/issues.rb
@@ -16,7 +16,8 @@ module API
labels = args.delete(:labels)
args[:label_name] = labels if match_all_labels
- args[:search] = "#{Issue.reference_prefix}#{args.delete(:iid)}" if args.key?(:iid)
+ # IssuesFinder expects iids
+ args[:iids] = args.delete(:iid) if args.key?(:iid)
issues = IssuesFinder.new(current_user, args).execute.inc_notes_with_associations
@@ -148,9 +149,7 @@ module API
issue = ::Issues::CreateService.new(user_project,
current_user,
issue_params.merge(request: request, api: true)).execute
- if issue.spam?
- render_api_error!({ error: 'Spam detected' }, 400)
- end
+ render_spam_error! if issue.spam?
if issue.valid?
present issue, with: ::API::Entities::Issue, current_user: current_user, project: user_project
@@ -181,9 +180,13 @@ module API
params.delete(:updated_at)
end
+ update_params = declared_params(include_missing: false).merge(request: request, api: true)
+
issue = ::Issues::UpdateService.new(user_project,
current_user,
- declared_params(include_missing: false)).execute(issue)
+ update_params).execute(issue)
+
+ render_spam_error! if issue.spam?
if issue.valid?
present issue, with: ::API::Entities::Issue, current_user: current_user, project: user_project
diff --git a/lib/api/v3/labels.rb b/lib/api/v3/labels.rb
new file mode 100644
index 00000000000..5c3261311bf
--- /dev/null
+++ b/lib/api/v3/labels.rb
@@ -0,0 +1,19 @@
+module API
+ module V3
+ class Labels < Grape::API
+ before { authenticate! }
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects do
+ desc 'Get all labels of the project' do
+ success ::API::Entities::Label
+ end
+ get ':id/labels' do
+ present available_labels, with: ::API::Entities::Label, current_user: current_user, project: user_project
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/members.rb b/lib/api/v3/members.rb
new file mode 100644
index 00000000000..19f276d5484
--- /dev/null
+++ b/lib/api/v3/members.rb
@@ -0,0 +1,133 @@
+module API
+ module V3
+ class Members < Grape::API
+ include PaginationParams
+
+ before { authenticate! }
+
+ helpers ::API::Helpers::MembersHelpers
+
+ %w[group project].each do |source_type|
+ params do
+ requires :id, type: String, desc: "The #{source_type} ID"
+ end
+ resource source_type.pluralize do
+ desc 'Gets a list of group or project members viewable by the authenticated user.' do
+ success ::API::Entities::Member
+ end
+ params do
+ optional :query, type: String, desc: 'A query string to search for members'
+ use :pagination
+ end
+ get ":id/members" do
+ source = find_source(source_type, params[:id])
+
+ users = source.users
+ users = users.merge(User.search(params[:query])) if params[:query]
+
+ present paginate(users), with: ::API::Entities::Member, source: source
+ end
+
+ desc 'Gets a member of a group or project.' do
+ success ::API::Entities::Member
+ end
+ params do
+ requires :user_id, type: Integer, desc: 'The user ID of the member'
+ end
+ get ":id/members/:user_id" do
+ source = find_source(source_type, params[:id])
+
+ members = source.members
+ member = members.find_by!(user_id: params[:user_id])
+
+ present member.user, with: ::API::Entities::Member, member: member
+ end
+
+ desc 'Adds a member to a group or project.' do
+ success ::API::Entities::Member
+ end
+ params do
+ requires :user_id, type: Integer, desc: 'The user ID of the new member'
+ requires :access_level, type: Integer, desc: 'A valid access level (defaults: `30`, developer access level)'
+ optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY'
+ end
+ post ":id/members" do
+ source = find_source(source_type, params[:id])
+ authorize_admin_source!(source_type, source)
+
+ member = source.members.find_by(user_id: params[:user_id])
+
+ # We need this explicit check because `source.add_user` doesn't
+ # currently return the member created so it would return 201 even if
+ # the member already existed...
+ # The `source_type == 'group'` check is to ensure back-compatibility
+ # but 409 behavior should be used for both project and group members in 9.0!
+ conflict!('Member already exists') if source_type == 'group' && member
+
+ unless member
+ member = source.add_user(params[:user_id], params[:access_level], current_user: current_user, expires_at: params[:expires_at])
+ end
+ if member.persisted? && member.valid?
+ present member.user, with: ::API::Entities::Member, member: member
+ else
+ # This is to ensure back-compatibility but 400 behavior should be used
+ # for all validation errors in 9.0!
+ render_api_error!('Access level is not known', 422) if member.errors.key?(:access_level)
+ render_validation_error!(member)
+ end
+ end
+
+ desc 'Updates a member of a group or project.' do
+ success ::API::Entities::Member
+ end
+ params do
+ requires :user_id, type: Integer, desc: 'The user ID of the new member'
+ requires :access_level, type: Integer, desc: 'A valid access level'
+ optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY'
+ end
+ put ":id/members/:user_id" do
+ source = find_source(source_type, params.delete(:id))
+ authorize_admin_source!(source_type, source)
+
+ member = source.members.find_by!(user_id: params.delete(:user_id))
+
+ if member.update_attributes(declared_params(include_missing: false))
+ present member.user, with: ::API::Entities::Member, member: member
+ else
+ # This is to ensure back-compatibility but 400 behavior should be used
+ # for all validation errors in 9.0!
+ render_api_error!('Access level is not known', 422) if member.errors.key?(:access_level)
+ render_validation_error!(member)
+ end
+ end
+
+ desc 'Removes a user from a group or project.'
+ params do
+ requires :user_id, type: Integer, desc: 'The user ID of the member'
+ end
+ delete ":id/members/:user_id" do
+ source = find_source(source_type, params[:id])
+
+ # This is to ensure back-compatibility but find_by! should be used
+ # in that casse in 9.0!
+ member = source.members.find_by(user_id: params[:user_id])
+
+ # This is to ensure back-compatibility but this should be removed in
+ # favor of find_by! in 9.0!
+ not_found!("Member: user_id:#{params[:user_id]}") if source_type == 'group' && member.nil?
+
+ # This is to ensure back-compatibility but 204 behavior should be used
+ # for all DELETE endpoints in 9.0!
+ if member.nil?
+ { message: "Access revoked", id: params[:user_id].to_i }
+ else
+ ::Members::DestroyService.new(source, current_user, declared_params).execute
+
+ present member.user, with: ::API::Entities::Member, member: member
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/notes.rb b/lib/api/v3/notes.rb
new file mode 100644
index 00000000000..0796bb62e68
--- /dev/null
+++ b/lib/api/v3/notes.rb
@@ -0,0 +1,148 @@
+module API
+ module V3
+ class Notes < Grape::API
+ include PaginationParams
+
+ before { authenticate! }
+
+ NOTEABLE_TYPES = [Issue, MergeRequest, Snippet].freeze
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects do
+ NOTEABLE_TYPES.each do |noteable_type|
+ noteables_str = noteable_type.to_s.underscore.pluralize
+
+ desc 'Get a list of project +noteable+ notes' do
+ success ::API::V3::Entities::Note
+ end
+ params do
+ requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
+ use :pagination
+ end
+ get ":id/#{noteables_str}/:noteable_id/notes" do
+ noteable = user_project.send(noteables_str.to_sym).find(params[:noteable_id])
+
+ if can?(current_user, noteable_read_ability_name(noteable), noteable)
+ # We exclude notes that are cross-references and that cannot be viewed
+ # by the current user. By doing this exclusion at this level and not
+ # at the DB query level (which we cannot in that case), the current
+ # page can have less elements than :per_page even if
+ # there's more than one page.
+ notes =
+ # paginate() only works with a relation. This could lead to a
+ # mismatch between the pagination headers info and the actual notes
+ # array returned, but this is really a edge-case.
+ paginate(noteable.notes).
+ reject { |n| n.cross_reference_not_visible_for?(current_user) }
+ present notes, with: ::API::V3::Entities::Note
+ else
+ not_found!("Notes")
+ end
+ end
+
+ desc 'Get a single +noteable+ note' do
+ success ::API::V3::Entities::Note
+ end
+ params do
+ requires :note_id, type: Integer, desc: 'The ID of a note'
+ requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
+ end
+ get ":id/#{noteables_str}/:noteable_id/notes/:note_id" do
+ noteable = user_project.send(noteables_str.to_sym).find(params[:noteable_id])
+ note = noteable.notes.find(params[:note_id])
+ can_read_note = can?(current_user, noteable_read_ability_name(noteable), noteable) && !note.cross_reference_not_visible_for?(current_user)
+
+ if can_read_note
+ present note, with: ::API::V3::Entities::Note
+ else
+ not_found!("Note")
+ end
+ end
+
+ desc 'Create a new +noteable+ note' do
+ success ::API::V3::Entities::Note
+ end
+ params do
+ requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
+ requires :body, type: String, desc: 'The content of a note'
+ optional :created_at, type: String, desc: 'The creation date of the note'
+ end
+ post ":id/#{noteables_str}/:noteable_id/notes" do
+ opts = {
+ note: params[:body],
+ noteable_type: noteables_str.classify,
+ noteable_id: params[:noteable_id]
+ }
+
+ noteable = user_project.send(noteables_str.to_sym).find(params[:noteable_id])
+
+ if can?(current_user, noteable_read_ability_name(noteable), noteable)
+ if params[:created_at] && (current_user.is_admin? || user_project.owner == current_user)
+ opts[:created_at] = params[:created_at]
+ end
+
+ note = ::Notes::CreateService.new(user_project, current_user, opts).execute
+ if note.valid?
+ present note, with: ::API::V3::Entities.const_get(note.class.name)
+ else
+ not_found!("Note #{note.errors.messages}")
+ end
+ else
+ not_found!("Note")
+ end
+ end
+
+ desc 'Update an existing +noteable+ note' do
+ success ::API::V3::Entities::Note
+ end
+ params do
+ requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
+ requires :note_id, type: Integer, desc: 'The ID of a note'
+ requires :body, type: String, desc: 'The content of a note'
+ end
+ put ":id/#{noteables_str}/:noteable_id/notes/:note_id" do
+ note = user_project.notes.find(params[:note_id])
+
+ authorize! :admin_note, note
+
+ opts = {
+ note: params[:body]
+ }
+
+ note = ::Notes::UpdateService.new(user_project, current_user, opts).execute(note)
+
+ if note.valid?
+ present note, with: ::API::V3::Entities::Note
+ else
+ render_api_error!("Failed to save note #{note.errors.messages}", 400)
+ end
+ end
+
+ desc 'Delete a +noteable+ note' do
+ success ::API::V3::Entities::Note
+ end
+ params do
+ requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
+ requires :note_id, type: Integer, desc: 'The ID of a note'
+ end
+ delete ":id/#{noteables_str}/:noteable_id/notes/:note_id" do
+ note = user_project.notes.find(params[:note_id])
+ authorize! :admin_note, note
+
+ ::Notes::DestroyService.new(user_project, current_user).execute(note)
+
+ present note, with: ::API::V3::Entities::Note
+ end
+ end
+ end
+
+ helpers do
+ def noteable_read_ability_name(noteable)
+ "read_#{noteable.class.to_s.underscore}".to_sym
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/project_snippets.rb b/lib/api/v3/project_snippets.rb
index 9f95d4395fa..e03e941d30b 100644
--- a/lib/api/v3/project_snippets.rb
+++ b/lib/api/v3/project_snippets.rb
@@ -64,6 +64,8 @@ module API
snippet = CreateSnippetService.new(user_project, current_user, snippet_params).execute
+ render_spam_error! if snippet.spam?
+
if snippet.persisted?
present snippet, with: ::API::V3::Entities::ProjectSnippet
else
@@ -93,12 +95,16 @@ module API
authorize! :update_project_snippet, snippet
snippet_params = declared_params(include_missing: false)
+ .merge(request: request, api: true)
+
snippet_params[:content] = snippet_params.delete(:code) if snippet_params[:code].present?
UpdateSnippetService.new(user_project, current_user, snippet,
snippet_params).execute
- if snippet.persisted?
+ render_spam_error! if snippet.spam?
+
+ if snippet.valid?
present snippet, with: ::API::V3::Entities::ProjectSnippet
else
render_validation_error!(snippet)
diff --git a/lib/api/v3/projects.rb b/lib/api/v3/projects.rb
index 6796da83f07..c3821555452 100644
--- a/lib/api/v3/projects.rb
+++ b/lib/api/v3/projects.rb
@@ -20,7 +20,8 @@ module API
optional :visibility_level, type: Integer, values: [
Gitlab::VisibilityLevel::PRIVATE,
Gitlab::VisibilityLevel::INTERNAL,
- Gitlab::VisibilityLevel::PUBLIC ], desc: 'Create a public project. The same as visibility_level = 20.'
+ Gitlab::VisibilityLevel::PUBLIC
+ ], desc: 'Create a public project. The same as visibility_level = 20.'
optional :public_builds, type: Boolean, desc: 'Perform public builds'
optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access'
optional :only_allow_merge_if_build_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed'
@@ -232,13 +233,13 @@ module API
end
desc 'Get events for a single project' do
- success ::API::Entities::Event
+ success ::API::V3::Entities::Event
end
params do
use :pagination
end
get ":id/events" do
- present paginate(user_project.events.recent), with: ::API::Entities::Event
+ present paginate(user_project.events.recent), with: ::API::V3::Entities::Event
end
desc 'Fork new project for the current user or provided namespace.' do
diff --git a/lib/api/v3/repositories.rb b/lib/api/v3/repositories.rb
new file mode 100644
index 00000000000..3549ea225ef
--- /dev/null
+++ b/lib/api/v3/repositories.rb
@@ -0,0 +1,55 @@
+require 'mime/types'
+
+module API
+ module V3
+ class Repositories < Grape::API
+ before { authorize! :download_code, user_project }
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects do
+ helpers do
+ def handle_project_member_errors(errors)
+ if errors[:project_access].any?
+ error!(errors[:project_access], 422)
+ end
+ not_found!
+ end
+ end
+
+ desc 'Get a project repository tree' do
+ success ::API::Entities::RepoTreeObject
+ end
+ params do
+ optional :ref_name, type: String, desc: 'The name of a repository branch or tag, if not given the default branch is used'
+ optional :path, type: String, desc: 'The path of the tree'
+ optional :recursive, type: Boolean, default: false, desc: 'Used to get a recursive tree'
+ end
+ get ':id/repository/tree' do
+ ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
+ path = params[:path] || nil
+
+ commit = user_project.commit(ref)
+ not_found!('Tree') unless commit
+
+ tree = user_project.repository.tree(commit.id, path, recursive: params[:recursive])
+
+ present tree.sorted_entries, with: ::API::Entities::RepoTreeObject
+ end
+
+ desc 'Get repository contributors' do
+ success ::API::Entities::Contributor
+ end
+ get ':id/repository/contributors' do
+ begin
+ present user_project.repository.contributors,
+ with: ::API::Entities::Contributor
+ rescue
+ not_found!
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/subscriptions.rb b/lib/api/v3/subscriptions.rb
new file mode 100644
index 00000000000..02a4157c26e
--- /dev/null
+++ b/lib/api/v3/subscriptions.rb
@@ -0,0 +1,53 @@
+module API
+ module V3
+ class Subscriptions < Grape::API
+ before { authenticate! }
+
+ subscribable_types = {
+ 'merge_request' => proc { |id| find_merge_request_with_access(id, :update_merge_request) },
+ 'merge_requests' => proc { |id| find_merge_request_with_access(id, :update_merge_request) },
+ 'issues' => proc { |id| find_project_issue(id) },
+ 'labels' => proc { |id| find_project_label(id) },
+ }
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ requires :subscribable_id, type: String, desc: 'The ID of a resource'
+ end
+ resource :projects do
+ subscribable_types.each do |type, finder|
+ type_singularized = type.singularize
+ entity_class = ::API::Entities.const_get(type_singularized.camelcase)
+
+ desc 'Subscribe to a resource' do
+ success entity_class
+ end
+ post ":id/#{type}/:subscribable_id/subscription" do
+ resource = instance_exec(params[:subscribable_id], &finder)
+
+ if resource.subscribed?(current_user, user_project)
+ not_modified!
+ else
+ resource.subscribe(current_user, user_project)
+ present resource, with: entity_class, current_user: current_user, project: user_project
+ end
+ end
+
+ desc 'Unsubscribe from a resource' do
+ success entity_class
+ end
+ delete ":id/#{type}/:subscribable_id/subscription" do
+ resource = instance_exec(params[:subscribable_id], &finder)
+
+ if !resource.subscribed?(current_user, user_project)
+ not_modified!
+ else
+ resource.unsubscribe(current_user, user_project)
+ present resource, with: entity_class, current_user: current_user, project: user_project
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/system_hooks.rb b/lib/api/v3/system_hooks.rb
new file mode 100644
index 00000000000..391510b9ee0
--- /dev/null
+++ b/lib/api/v3/system_hooks.rb
@@ -0,0 +1,19 @@
+module API
+ module V3
+ class SystemHooks < Grape::API
+ before do
+ authenticate!
+ authenticated_as_admin!
+ end
+
+ resource :hooks do
+ desc 'Get the list of system hooks' do
+ success ::API::Entities::Hook
+ end
+ get do
+ present SystemHook.all, with: ::API::Entities::Hook
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/tags.rb b/lib/api/v3/tags.rb
new file mode 100644
index 00000000000..016e3d86932
--- /dev/null
+++ b/lib/api/v3/tags.rb
@@ -0,0 +1,20 @@
+module API
+ module V3
+ class Tags < Grape::API
+ before { authorize! :download_code, user_project }
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects do
+ desc 'Get a project repository tags' do
+ success ::API::Entities::RepoTag
+ end
+ get ":id/repository/tags" do
+ tags = user_project.repository.tags.sort_by(&:name).reverse
+ present tags, with: ::API::Entities::RepoTag, project: user_project
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/templates.rb b/lib/api/v3/templates.rb
new file mode 100644
index 00000000000..4c577a8d2b7
--- /dev/null
+++ b/lib/api/v3/templates.rb
@@ -0,0 +1,122 @@
+module API
+ module V3
+ class Templates < Grape::API
+ GLOBAL_TEMPLATE_TYPES = {
+ gitignores: {
+ klass: Gitlab::Template::GitignoreTemplate,
+ gitlab_version: 8.8
+ },
+ gitlab_ci_ymls: {
+ klass: Gitlab::Template::GitlabCiYmlTemplate,
+ gitlab_version: 8.9
+ },
+ dockerfiles: {
+ klass: Gitlab::Template::DockerfileTemplate,
+ gitlab_version: 8.15
+ }
+ }.freeze
+ PROJECT_TEMPLATE_REGEX =
+ /[\<\{\[]
+ (project|description|
+ one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here
+ [\>\}\]]/xi.freeze
+ YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i.freeze
+ FULLNAME_TEMPLATE_REGEX =
+ /[\<\{\[]
+ (fullname|name\sof\s(author|copyright\sowner))
+ [\>\}\]]/xi.freeze
+ DEPRECATION_MESSAGE = ' This endpoint is deprecated and has been removed in V4.'.freeze
+
+ helpers do
+ def parsed_license_template
+ # We create a fresh Licensee::License object since we'll modify its
+ # content in place below.
+ template = Licensee::License.new(params[:name])
+
+ template.content.gsub!(YEAR_TEMPLATE_REGEX, Time.now.year.to_s)
+ template.content.gsub!(PROJECT_TEMPLATE_REGEX, params[:project]) if params[:project].present?
+
+ fullname = params[:fullname].presence || current_user.try(:name)
+ template.content.gsub!(FULLNAME_TEMPLATE_REGEX, fullname) if fullname
+ template
+ end
+
+ def render_response(template_type, template)
+ not_found!(template_type.to_s.singularize) unless template
+ present template, with: ::API::Entities::Template
+ end
+ end
+
+ { "licenses" => :deprecated, "templates/licenses" => :ok }.each do |route, status|
+ desc 'Get the list of the available license template' do
+ detailed_desc = 'This feature was introduced in GitLab 8.7.'
+ detailed_desc << DEPRECATION_MESSAGE unless status == :ok
+ detail detailed_desc
+ success ::API::Entities::RepoLicense
+ end
+ params do
+ optional :popular, type: Boolean, desc: 'If passed, returns only popular licenses'
+ end
+ get route do
+ options = {
+ featured: declared(params).popular.present? ? true : nil
+ }
+ present Licensee::License.all(options), with: ::API::Entities::RepoLicense
+ end
+ end
+
+ { "licenses/:name" => :deprecated, "templates/licenses/:name" => :ok }.each do |route, status|
+ desc 'Get the text for a specific license' do
+ detailed_desc = 'This feature was introduced in GitLab 8.7.'
+ detailed_desc << DEPRECATION_MESSAGE unless status == :ok
+ detail detailed_desc
+ success ::API::Entities::RepoLicense
+ end
+ params do
+ requires :name, type: String, desc: 'The name of the template'
+ end
+ get route, requirements: { name: /[\w\.-]+/ } do
+ not_found!('License') unless Licensee::License.find(declared(params).name)
+
+ template = parsed_license_template
+
+ present template, with: ::API::Entities::RepoLicense
+ end
+ end
+
+ GLOBAL_TEMPLATE_TYPES.each do |template_type, properties|
+ klass = properties[:klass]
+ gitlab_version = properties[:gitlab_version]
+
+ { template_type => :deprecated, "templates/#{template_type}" => :ok }.each do |route, status|
+ desc 'Get the list of the available template' do
+ detailed_desc = "This feature was introduced in GitLab #{gitlab_version}."
+ detailed_desc << DEPRECATION_MESSAGE unless status == :ok
+ detail detailed_desc
+ success ::API::Entities::TemplatesList
+ end
+ get route do
+ present klass.all, with: ::API::Entities::TemplatesList
+ end
+ end
+
+ { "#{template_type}/:name" => :deprecated, "templates/#{template_type}/:name" => :ok }.each do |route, status|
+ desc 'Get the text for a specific template present in local filesystem' do
+ detailed_desc = "This feature was introduced in GitLab #{gitlab_version}."
+ detailed_desc << DEPRECATION_MESSAGE unless status == :ok
+ detail detailed_desc
+ success ::API::Entities::Template
+ end
+ params do
+ requires :name, type: String, desc: 'The name of the template'
+ end
+ get route do
+ new_template = klass.find(declared(params).name)
+
+ render_response(template_type, new_template)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/todos.rb b/lib/api/v3/todos.rb
new file mode 100644
index 00000000000..4f9b5fe72a6
--- /dev/null
+++ b/lib/api/v3/todos.rb
@@ -0,0 +1,28 @@
+module API
+ module V3
+ class Todos < Grape::API
+ before { authenticate! }
+
+ resource :todos do
+ desc 'Mark a todo as done' do
+ success ::API::Entities::Todo
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the todo being marked as done'
+ end
+ delete ':id' do
+ todo = current_user.todos.find(params[:id])
+ TodoService.new.mark_todos_as_done([todo], current_user)
+
+ present todo.reload, with: ::API::Entities::Todo, current_user: current_user
+ end
+
+ desc 'Mark all todos as done'
+ delete do
+ todos = TodosFinder.new(current_user, params).execute
+ TodoService.new.mark_todos_as_done(todos, current_user)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/users.rb b/lib/api/v3/users.rb
new file mode 100644
index 00000000000..7838cdc46a7
--- /dev/null
+++ b/lib/api/v3/users.rb
@@ -0,0 +1,117 @@
+module API
+ module V3
+ class Users < Grape::API
+ include PaginationParams
+
+ before do
+ allow_access_with_scope :read_user if request.get?
+ authenticate!
+ end
+
+ resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do
+ desc 'Get the SSH keys of a specified user. Available only for admins.' do
+ success ::API::Entities::SSHKey
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ use :pagination
+ end
+ get ':id/keys' do
+ authenticated_as_admin!
+
+ user = User.find_by(id: params[:id])
+ not_found!('User') unless user
+
+ present paginate(user.keys), with: ::API::Entities::SSHKey
+ end
+
+ desc 'Get the emails addresses of a specified user. Available only for admins.' do
+ success ::API::Entities::Email
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ use :pagination
+ end
+ get ':id/emails' do
+ authenticated_as_admin!
+ user = User.find_by(id: params[:id])
+ not_found!('User') unless user
+
+ present user.emails, with: ::API::Entities::Email
+ end
+
+ desc 'Block a user. Available only for admins.'
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ end
+ put ':id/block' do
+ authenticated_as_admin!
+ user = User.find_by(id: params[:id])
+ not_found!('User') unless user
+
+ if !user.ldap_blocked?
+ user.block
+ else
+ forbidden!('LDAP blocked users cannot be modified by the API')
+ end
+ end
+
+ desc 'Unblock a user. Available only for admins.'
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ end
+ put ':id/unblock' do
+ authenticated_as_admin!
+ user = User.find_by(id: params[:id])
+ not_found!('User') unless user
+
+ if user.ldap_blocked?
+ forbidden!('LDAP blocked users cannot be unblocked by the API')
+ else
+ user.activate
+ end
+ end
+
+ desc 'Get the contribution events of a specified user' do
+ detail 'This feature was introduced in GitLab 8.13.'
+ success ::API::V3::Entities::Event
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ use :pagination
+ end
+ get ':id/events' do
+ user = User.find_by(id: params[:id])
+ not_found!('User') unless user
+
+ events = user.events.
+ merge(ProjectsFinder.new.execute(current_user)).
+ references(:project).
+ with_associations.
+ recent
+
+ present paginate(events), with: ::API::V3::Entities::Event
+ end
+ end
+
+ resource :user do
+ desc "Get the currently authenticated user's SSH keys" do
+ success ::API::Entities::SSHKey
+ end
+ params do
+ use :pagination
+ end
+ get "keys" do
+ present current_user.keys, with: ::API::Entities::SSHKey
+ end
+
+ desc "Get the currently authenticated user's email addresses" do
+ success ::API::Entities::Email
+ end
+ get "emails" do
+ present current_user.emails, with: ::API::Entities::Email
+ end
+ end
+ end
+ end
+end
diff --git a/lib/backup/database.rb b/lib/backup/database.rb
index 22319ec6623..4016ac76348 100644
--- a/lib/backup/database.rb
+++ b/lib/backup/database.rb
@@ -5,7 +5,7 @@ module Backup
attr_reader :config, :db_file_name
def initialize
- @config = YAML.load_file(File.join(Rails.root,'config','database.yml'))[Rails.env]
+ @config = YAML.load_file(File.join(Rails.root, 'config', 'database.yml'))[Rails.env]
@db_file_name = File.join(Gitlab.config.backup.path, 'db', 'database.sql.gz')
end
@@ -13,28 +13,32 @@ module Backup
FileUtils.mkdir_p(File.dirname(db_file_name))
FileUtils.rm_f(db_file_name)
compress_rd, compress_wr = IO.pipe
- compress_pid = spawn(*%W(gzip -1 -c), in: compress_rd, out: [db_file_name, 'w', 0600])
+ compress_pid = spawn(*%w(gzip -1 -c), in: compress_rd, out: [db_file_name, 'w', 0600])
compress_rd.close
- dump_pid = case config["adapter"]
- when /^mysql/ then
- $progress.print "Dumping MySQL database #{config['database']} ... "
- # Workaround warnings from MySQL 5.6 about passwords on cmd line
- ENV['MYSQL_PWD'] = config["password"].to_s if config["password"]
- spawn('mysqldump', *mysql_args, config['database'], out: compress_wr)
- when "postgresql" then
- $progress.print "Dumping PostgreSQL database #{config['database']} ... "
- pg_env
- pgsql_args = ["--clean"] # Pass '--clean' to include 'DROP TABLE' statements in the DB dump.
- if Gitlab.config.backup.pg_schema
- pgsql_args << "-n"
- pgsql_args << Gitlab.config.backup.pg_schema
+ dump_pid =
+ case config["adapter"]
+ when /^mysql/ then
+ $progress.print "Dumping MySQL database #{config['database']} ... "
+ # Workaround warnings from MySQL 5.6 about passwords on cmd line
+ ENV['MYSQL_PWD'] = config["password"].to_s if config["password"]
+ spawn('mysqldump', *mysql_args, config['database'], out: compress_wr)
+ when "postgresql" then
+ $progress.print "Dumping PostgreSQL database #{config['database']} ... "
+ pg_env
+ pgsql_args = ["--clean"] # Pass '--clean' to include 'DROP TABLE' statements in the DB dump.
+ if Gitlab.config.backup.pg_schema
+ pgsql_args << "-n"
+ pgsql_args << Gitlab.config.backup.pg_schema
+ end
+ spawn('pg_dump', *pgsql_args, config['database'], out: compress_wr)
end
- spawn('pg_dump', *pgsql_args, config['database'], out: compress_wr)
- end
compress_wr.close
- success = [compress_pid, dump_pid].all? { |pid| Process.waitpid(pid); $?.success? }
+ success = [compress_pid, dump_pid].all? do |pid|
+ Process.waitpid(pid)
+ $?.success?
+ end
report_success(success)
abort 'Backup failed' unless success
@@ -42,23 +46,27 @@ module Backup
def restore
decompress_rd, decompress_wr = IO.pipe
- decompress_pid = spawn(*%W(gzip -cd), out: decompress_wr, in: db_file_name)
+ decompress_pid = spawn(*%w(gzip -cd), out: decompress_wr, in: db_file_name)
decompress_wr.close
- restore_pid = case config["adapter"]
- when /^mysql/ then
- $progress.print "Restoring MySQL database #{config['database']} ... "
- # Workaround warnings from MySQL 5.6 about passwords on cmd line
- ENV['MYSQL_PWD'] = config["password"].to_s if config["password"]
- spawn('mysql', *mysql_args, config['database'], in: decompress_rd)
- when "postgresql" then
- $progress.print "Restoring PostgreSQL database #{config['database']} ... "
- pg_env
- spawn('psql', config['database'], in: decompress_rd)
- end
+ restore_pid =
+ case config["adapter"]
+ when /^mysql/ then
+ $progress.print "Restoring MySQL database #{config['database']} ... "
+ # Workaround warnings from MySQL 5.6 about passwords on cmd line
+ ENV['MYSQL_PWD'] = config["password"].to_s if config["password"]
+ spawn('mysql', *mysql_args, config['database'], in: decompress_rd)
+ when "postgresql" then
+ $progress.print "Restoring PostgreSQL database #{config['database']} ... "
+ pg_env
+ spawn('psql', config['database'], in: decompress_rd)
+ end
decompress_rd.close
- success = [decompress_pid, restore_pid].all? { |pid| Process.waitpid(pid); $?.success? }
+ success = [decompress_pid, restore_pid].all? do |pid|
+ Process.waitpid(pid)
+ $?.success?
+ end
report_success(success)
abort 'Restore failed' unless success
diff --git a/lib/backup/files.rb b/lib/backup/files.rb
index cedbb289f6a..30a91647b77 100644
--- a/lib/backup/files.rb
+++ b/lib/backup/files.rb
@@ -8,6 +8,7 @@ module Backup
@name = name
@app_files_dir = File.realpath(app_files_dir)
@files_parent_dir = File.realpath(File.join(@app_files_dir, '..'))
+ @backup_files_dir = File.join(Gitlab.config.backup.path, File.basename(@app_files_dir) )
@backup_tarball = File.join(Gitlab.config.backup.path, name + '.tar.gz')
end
@@ -15,14 +16,28 @@ module Backup
def dump
FileUtils.mkdir_p(Gitlab.config.backup.path)
FileUtils.rm_f(backup_tarball)
- run_pipeline!([%W(tar -C #{app_files_dir} -cf - .), %W(gzip -c -1)], out: [backup_tarball, 'w', 0600])
+
+ if ENV['STRATEGY'] == 'copy'
+ cmd = %W(cp -a #{app_files_dir} #{Gitlab.config.backup.path})
+ output, status = Gitlab::Popen.popen(cmd)
+
+ unless status.zero?
+ puts output
+ abort 'Backup failed'
+ end
+
+ run_pipeline!([%W(tar -C #{@backup_files_dir} -cf - .), %w(gzip -c -1)], out: [backup_tarball, 'w', 0600])
+ FileUtils.rm_rf(@backup_files_dir)
+ else
+ run_pipeline!([%W(tar -C #{app_files_dir} -cf - .), %w(gzip -c -1)], out: [backup_tarball, 'w', 0600])
+ end
end
def restore
backup_existing_files_dir
create_files_dir
- run_pipeline!([%W(gzip -cd), %W(tar -C #{app_files_dir} -xf -)], in: backup_tarball)
+ run_pipeline!([%w(gzip -cd), %W(tar -C #{app_files_dir} -xf -)], in: backup_tarball)
end
def backup_existing_files_dir
@@ -32,7 +47,7 @@ module Backup
end
end
- def run_pipeline!(cmd_list, options={})
+ def run_pipeline!(cmd_list, options = {})
status_list = Open3.pipeline(*cmd_list, options)
abort 'Backup failed' unless status_list.compact.all?(&:success?)
end
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index f099c0651ac..5cc164a6325 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -1,8 +1,8 @@
module Backup
class Manager
- ARCHIVES_TO_BACKUP = %w[uploads builds artifacts pages lfs registry]
- FOLDERS_TO_BACKUP = %w[repositories db]
- FILE_NAME_SUFFIX = '_gitlab_backup.tar'
+ ARCHIVES_TO_BACKUP = %w[uploads builds artifacts pages lfs registry].freeze
+ FOLDERS_TO_BACKUP = %w[repositories db].freeze
+ FILE_NAME_SUFFIX = '_gitlab_backup.tar'.freeze
def pack
# Make sure there is a connection
@@ -20,13 +20,13 @@ module Backup
Dir.chdir(Gitlab.config.backup.path) do
File.open("#{Gitlab.config.backup.path}/backup_information.yml",
"w+") do |file|
- file << s.to_yaml.gsub(/^---\n/,'')
+ file << s.to_yaml.gsub(/^---\n/, '')
end
# create archive
$progress.print "Creating backup archive: #{tar_file} ... "
# Set file permissions on open to prevent chmod races.
- tar_system_options = {out: [tar_file, 'w', Gitlab.config.backup.archive_permissions]}
+ tar_system_options = { out: [tar_file, 'w', Gitlab.config.backup.archive_permissions] }
if Kernel.system('tar', '-cf', '-', *backup_contents, tar_system_options)
$progress.puts "done".color(:green)
else
@@ -50,8 +50,8 @@ module Backup
directory = connect_to_remote_directory(connection_settings)
if directory.files.create(key: tar_file, body: File.open(tar_file), public: false,
- multipart_chunk_size: Gitlab.config.backup.upload.multipart_chunk_size,
- encryption: Gitlab.config.backup.upload.encryption)
+ multipart_chunk_size: Gitlab.config.backup.upload.multipart_chunk_size,
+ encryption: Gitlab.config.backup.upload.encryption)
$progress.puts "done".color(:green)
else
puts "uploading backup to #{remote_directory} failed".color(:red)
@@ -123,11 +123,11 @@ module Backup
exit 1
end
- if ENV['BACKUP'].present?
- tar_file = "#{ENV['BACKUP']}#{FILE_NAME_SUFFIX}"
- else
- tar_file = file_list.first
- end
+ tar_file = if ENV['BACKUP'].present?
+ "#{ENV['BACKUP']}#{FILE_NAME_SUFFIX}"
+ else
+ file_list.first
+ end
unless File.exist?(tar_file)
$progress.puts "The backup file #{tar_file} does not exist!"
@@ -158,7 +158,7 @@ module Backup
end
def tar_version
- tar_version, _ = Gitlab::Popen.popen(%W(tar --version))
+ tar_version, _ = Gitlab::Popen.popen(%w(tar --version))
tar_version.force_encoding('locale').split("\n").first
end
diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb
index d746070913d..d16d5ba4960 100644
--- a/lib/backup/repository.rb
+++ b/lib/backup/repository.rb
@@ -2,7 +2,7 @@ require 'yaml'
module Backup
class Repository
-
+ # rubocop:disable Metrics/AbcSize
def dump
prepare
@@ -12,7 +12,7 @@ module Backup
path_to_project_bundle = path_to_bundle(project)
# Create namespace dir if missing
- FileUtils.mkdir_p(File.join(backup_repos_path, project.namespace.path)) if project.namespace
+ FileUtils.mkdir_p(File.join(backup_repos_path, project.namespace.full_path)) if project.namespace
if project.empty_repo?
$progress.puts "[SKIPPED]".color(:cyan)
@@ -85,11 +85,11 @@ module Backup
project.ensure_dir_exist
- if File.exists?(path_to_project_bundle)
- cmd = %W(#{Gitlab.config.git.bin_path} clone --bare #{path_to_project_bundle} #{path_to_project_repo})
- else
- cmd = %W(#{Gitlab.config.git.bin_path} init --bare #{path_to_project_repo})
- end
+ cmd = if File.exist?(path_to_project_bundle)
+ %W(#{Gitlab.config.git.bin_path} clone --bare #{path_to_project_bundle} #{path_to_project_repo})
+ else
+ %W(#{Gitlab.config.git.bin_path} init --bare #{path_to_project_repo})
+ end
output, status = Gitlab::Popen.popen(cmd)
if status.zero?
@@ -150,6 +150,7 @@ module Backup
puts output
end
end
+ # rubocop:enable Metrics/AbcSize
protected
@@ -193,7 +194,7 @@ module Backup
end
def silent
- {err: '/dev/null', out: '/dev/null'}
+ { err: '/dev/null', out: '/dev/null' }
end
private
diff --git a/lib/backup/uploads.rb b/lib/backup/uploads.rb
index 9261f77f3c9..35118375499 100644
--- a/lib/backup/uploads.rb
+++ b/lib/backup/uploads.rb
@@ -2,7 +2,6 @@ require 'backup/files'
module Backup
class Uploads < Files
-
def initialize
super('uploads', Rails.root.join('public/uploads'))
end
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index a3d495a5da0..02d5ad70fa7 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -33,7 +33,12 @@ module Banzai
# Returns a String replaced with the return of the block.
def self.references_in(text, pattern = object_class.reference_pattern)
text.gsub(pattern) do |match|
- yield match, $~[object_sym].to_i, $~[:project], $~[:namespace], $~
+ symbol = $~[object_sym]
+ if object_class.reference_valid?(symbol)
+ yield match, symbol.to_i, $~[:project], $~[:namespace], $~
+ else
+ match
+ end
end
end
@@ -155,11 +160,12 @@ module Banzai
data = data_attributes_for(link_content || match, project, object, link: !!link_content)
- if matches.names.include?("url") && matches[:url]
- url = matches[:url]
- else
- url = url_for_object_cached(object, project)
- end
+ url =
+ if matches.names.include?("url") && matches[:url]
+ matches[:url]
+ else
+ url_for_object_cached(object, project)
+ end
content = link_content || object_link_text(object, matches)
@@ -285,7 +291,7 @@ module Banzai
end
def current_project_namespace_path
- @current_project_namespace_path ||= project.namespace.path
+ @current_project_namespace_path ||= project.namespace.full_path
end
private
diff --git a/lib/banzai/filter/autolink_filter.rb b/lib/banzai/filter/autolink_filter.rb
index 80c844baecd..b8d2673c1a6 100644
--- a/lib/banzai/filter/autolink_filter.rb
+++ b/lib/banzai/filter/autolink_filter.rb
@@ -37,7 +37,7 @@ module Banzai
and contains(., '://')
and not(starts-with(., 'http'))
and not(starts-with(., 'ftp'))
- ])
+ ]).freeze
def call
return doc if context[:autolink] == false
diff --git a/lib/banzai/filter/gollum_tags_filter.rb b/lib/banzai/filter/gollum_tags_filter.rb
index d08267a9d6c..0ea4eeaed5b 100644
--- a/lib/banzai/filter/gollum_tags_filter.rb
+++ b/lib/banzai/filter/gollum_tags_filter.rb
@@ -149,11 +149,12 @@ module Banzai
name, reference = *parts.compact.map(&:strip)
end
- if url?(reference)
- href = reference
- else
- href = ::File.join(project_wiki_base_path, reference)
- end
+ href =
+ if url?(reference)
+ reference
+ else
+ ::File.join(project_wiki_base_path, reference)
+ end
content_tag(:a, name || reference, href: href, class: 'gfm')
end
diff --git a/lib/banzai/filter/issue_reference_filter.rb b/lib/banzai/filter/issue_reference_filter.rb
index fd6b9704132..044d18ff824 100644
--- a/lib/banzai/filter/issue_reference_filter.rb
+++ b/lib/banzai/filter/issue_reference_filter.rb
@@ -39,11 +39,12 @@ module Banzai
projects_per_reference.each do |path, project|
issue_ids = references_per_project[path]
- if project.default_issues_tracker?
- issues = project.issues.where(iid: issue_ids.to_a)
- else
- issues = issue_ids.map { |id| ExternalIssue.new(id, project) }
- end
+ issues =
+ if project.default_issues_tracker?
+ project.issues.where(iid: issue_ids.to_a)
+ else
+ issue_ids.map { |id| ExternalIssue.new(id, project) }
+ end
issues.each do |issue|
hash[project][issue.iid.to_i] = issue
diff --git a/lib/banzai/filter/plantuml_filter.rb b/lib/banzai/filter/plantuml_filter.rb
index e194cf59275..b2537117558 100644
--- a/lib/banzai/filter/plantuml_filter.rb
+++ b/lib/banzai/filter/plantuml_filter.rb
@@ -7,7 +7,7 @@ module Banzai
#
class PlantumlFilter < HTML::Pipeline::Filter
def call
- return doc unless doc.at('pre.plantuml') and settings.plantuml_enabled
+ return doc unless doc.at('pre.plantuml') && settings.plantuml_enabled
plantuml_setup
diff --git a/lib/banzai/filter/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb
index 1aa9355b256..c973897f420 100644
--- a/lib/banzai/filter/user_reference_filter.rb
+++ b/lib/banzai/filter/user_reference_filter.rb
@@ -75,8 +75,8 @@ module Banzai
# corresponding Namespace objects.
def namespaces
@namespaces ||=
- Namespace.where(path: usernames).each_with_object({}) do |row, hash|
- hash[row.path] = row
+ Namespace.where_full_path_in(usernames).each_with_object({}) do |row, hash|
+ hash[row.full_path] = row
end
end
@@ -122,7 +122,7 @@ module Banzai
def link_to_namespace(namespace, link_content: nil)
if namespace.is_a?(Group)
- link_to_group(namespace.path, namespace, link_content: link_content)
+ link_to_group(namespace.full_path, namespace, link_content: link_content)
else
link_to_user(namespace.path, namespace, link_content: link_content)
end
diff --git a/lib/banzai/querying.rb b/lib/banzai/querying.rb
index 1e1b51e683e..fb2faae02bc 100644
--- a/lib/banzai/querying.rb
+++ b/lib/banzai/querying.rb
@@ -1,18 +1,64 @@
module Banzai
module Querying
+ module_function
+
# Searches a Nokogiri document using a CSS query, optionally optimizing it
# whenever possible.
#
- # document - A document/element to search.
- # query - The CSS query to use.
+ # document - A document/element to search.
+ # query - The CSS query to use.
+ # reference_options - A hash with nodes filter options
#
- # Returns a Nokogiri::XML::NodeSet.
- def self.css(document, query)
+ # Returns an array of Nokogiri::XML::Element objects if location is specified
+ # in reference_options. Otherwise it would a Nokogiri::XML::NodeSet.
+ def css(document, query, reference_options = {})
# When using "a.foo" Nokogiri compiles this to "//a[...]" but
# "descendant::a[...]" is quite a bit faster and achieves the same result.
xpath = Nokogiri::CSS.xpath_for(query)[0].gsub(%r{^//}, 'descendant::')
+ xpath = restrict_to_p_nodes_at_root(xpath) if filter_nodes_at_beginning?(reference_options)
+ nodes = document.xpath(xpath)
+
+ filter_nodes(nodes, reference_options)
+ end
+
+ def restrict_to_p_nodes_at_root(xpath)
+ xpath.gsub('descendant::', './p/')
+ end
+
+ def filter_nodes(nodes, reference_options)
+ if filter_nodes_at_beginning?(reference_options)
+ filter_nodes_at_beginning(nodes)
+ else
+ nodes
+ end
+ end
+
+ def filter_nodes_at_beginning?(reference_options)
+ reference_options && reference_options[:location] == :beginning
+ end
+
+ # Selects child nodes if they are present in the beginning among other siblings.
+ #
+ # nodes - A Nokogiri::XML::NodeSet.
+ #
+ # Returns an array of Nokogiri::XML::Element objects.
+ def filter_nodes_at_beginning(nodes)
+ parents_and_nodes = nodes.group_by(&:parent)
+ filtered_nodes = []
+
+ parents_and_nodes.each do |parent, nodes|
+ children = parent.children
+ nodes = nodes.to_a
+
+ children.each do |child|
+ next if child.text.blank?
+ node = nodes.shift
+ break unless node == child
+ filtered_nodes << node
+ end
+ end
- document.xpath(xpath)
+ filtered_nodes
end
end
end
diff --git a/lib/banzai/reference_extractor.rb b/lib/banzai/reference_extractor.rb
index b26a41a1f3b..8e3b0c4db79 100644
--- a/lib/banzai/reference_extractor.rb
+++ b/lib/banzai/reference_extractor.rb
@@ -16,6 +16,11 @@ module Banzai
processor.process(html_documents)
end
+ def reset_memoized_values
+ @html_documents = nil
+ @texts_and_contexts = []
+ end
+
private
def html_documents
diff --git a/lib/banzai/reference_parser/base_parser.rb b/lib/banzai/reference_parser/base_parser.rb
index d8a855ec1fe..2058a58d0ae 100644
--- a/lib/banzai/reference_parser/base_parser.rb
+++ b/lib/banzai/reference_parser/base_parser.rb
@@ -33,7 +33,7 @@ module Banzai
# they have access to.
class BaseParser
class << self
- attr_accessor :reference_type
+ attr_accessor :reference_type, :reference_options
end
# Returns the attribute name containing the value for every object to be
@@ -182,9 +182,10 @@ module Banzai
# the references.
def process(documents)
type = self.class.reference_type
+ reference_options = self.class.reference_options
nodes = documents.flat_map do |document|
- Querying.css(document, "a[data-reference-type='#{type}'].gfm").to_a
+ Querying.css(document, "a[data-reference-type='#{type}'].gfm", reference_options).to_a
end
gather_references(nodes)
diff --git a/lib/banzai/reference_parser/directly_addressed_user_parser.rb b/lib/banzai/reference_parser/directly_addressed_user_parser.rb
new file mode 100644
index 00000000000..77df9bbd024
--- /dev/null
+++ b/lib/banzai/reference_parser/directly_addressed_user_parser.rb
@@ -0,0 +1,8 @@
+module Banzai
+ module ReferenceParser
+ class DirectlyAddressedUserParser < UserParser
+ self.reference_type = :user
+ self.reference_options = { location: :beginning }
+ end
+ end
+end
diff --git a/lib/bitbucket/connection.rb b/lib/bitbucket/connection.rb
index 7e55cf4deab..b9279c33f5b 100644
--- a/lib/bitbucket/connection.rb
+++ b/lib/bitbucket/connection.rb
@@ -1,8 +1,8 @@
module Bitbucket
class Connection
- DEFAULT_API_VERSION = '2.0'
- DEFAULT_BASE_URI = 'https://api.bitbucket.org/'
- DEFAULT_QUERY = {}
+ DEFAULT_API_VERSION = '2.0'.freeze
+ DEFAULT_BASE_URI = 'https://api.bitbucket.org/'.freeze
+ DEFAULT_QUERY = {}.freeze
attr_reader :expires_at, :expires_in, :refresh_token, :token
@@ -24,9 +24,7 @@ module Bitbucket
response.parsed
end
- def expired?
- connection.expired?
- end
+ delegate :expired?, to: :connection
def refresh!
response = connection.refresh!
diff --git a/lib/bitbucket/representation/repo.rb b/lib/bitbucket/representation/repo.rb
index 423eff8f2a5..59b0fda8e14 100644
--- a/lib/bitbucket/representation/repo.rb
+++ b/lib/bitbucket/representation/repo.rb
@@ -23,7 +23,7 @@ module Bitbucket
url = raw['links']['clone'].find { |link| link['name'] == 'https' }.fetch('href')
if token.present?
- clone_url = URI::parse(url)
+ clone_url = URI.parse(url)
clone_url.user = "x-token-auth:#{token}"
clone_url.to_s
else
diff --git a/lib/ci/ansi2html.rb b/lib/ci/ansi2html.rb
index c10d3616f31..b3ccad7b28d 100644
--- a/lib/ci/ansi2html.rb
+++ b/lib/ci/ansi2html.rb
@@ -13,7 +13,7 @@ module Ci
5 => 'magenta',
6 => 'cyan',
7 => 'white', # not that this is gray in the dark (aka default) color table
- }
+ }.freeze
STYLE_SWITCHES = {
bold: 0x01,
@@ -21,7 +21,7 @@ module Ci
underline: 0x04,
conceal: 0x08,
cross: 0x10,
- }
+ }.freeze
def self.convert(ansi, state = nil)
Converter.new.convert(ansi, state)
@@ -29,64 +29,108 @@ module Ci
class Converter
def on_0(s) reset() end
+
def on_1(s) enable(STYLE_SWITCHES[:bold]) end
+
def on_3(s) enable(STYLE_SWITCHES[:italic]) end
+
def on_4(s) enable(STYLE_SWITCHES[:underline]) end
+
def on_8(s) enable(STYLE_SWITCHES[:conceal]) end
+
def on_9(s) enable(STYLE_SWITCHES[:cross]) end
def on_21(s) disable(STYLE_SWITCHES[:bold]) end
+
def on_22(s) disable(STYLE_SWITCHES[:bold]) end
+
def on_23(s) disable(STYLE_SWITCHES[:italic]) end
+
def on_24(s) disable(STYLE_SWITCHES[:underline]) end
+
def on_28(s) disable(STYLE_SWITCHES[:conceal]) end
+
def on_29(s) disable(STYLE_SWITCHES[:cross]) end
def on_30(s) set_fg_color(0) end
+
def on_31(s) set_fg_color(1) end
+
def on_32(s) set_fg_color(2) end
+
def on_33(s) set_fg_color(3) end
+
def on_34(s) set_fg_color(4) end
+
def on_35(s) set_fg_color(5) end
+
def on_36(s) set_fg_color(6) end
+
def on_37(s) set_fg_color(7) end
+
def on_38(s) set_fg_color_256(s) end
+
def on_39(s) set_fg_color(9) end
def on_40(s) set_bg_color(0) end
+
def on_41(s) set_bg_color(1) end
+
def on_42(s) set_bg_color(2) end
+
def on_43(s) set_bg_color(3) end
+
def on_44(s) set_bg_color(4) end
+
def on_45(s) set_bg_color(5) end
+
def on_46(s) set_bg_color(6) end
+
def on_47(s) set_bg_color(7) end
+
def on_48(s) set_bg_color_256(s) end
+
def on_49(s) set_bg_color(9) end
def on_90(s) set_fg_color(0, 'l') end
+
def on_91(s) set_fg_color(1, 'l') end
+
def on_92(s) set_fg_color(2, 'l') end
+
def on_93(s) set_fg_color(3, 'l') end
+
def on_94(s) set_fg_color(4, 'l') end
+
def on_95(s) set_fg_color(5, 'l') end
+
def on_96(s) set_fg_color(6, 'l') end
+
def on_97(s) set_fg_color(7, 'l') end
+
def on_99(s) set_fg_color(9, 'l') end
def on_100(s) set_bg_color(0, 'l') end
+
def on_101(s) set_bg_color(1, 'l') end
+
def on_102(s) set_bg_color(2, 'l') end
+
def on_103(s) set_bg_color(3, 'l') end
+
def on_104(s) set_bg_color(4, 'l') end
+
def on_105(s) set_bg_color(5, 'l') end
+
def on_106(s) set_bg_color(6, 'l') end
+
def on_107(s) set_bg_color(7, 'l') end
+
def on_109(s) set_bg_color(9, 'l') end
attr_accessor :offset, :n_open_tags, :fg_color, :bg_color, :style_mask
- STATE_PARAMS = [:offset, :n_open_tags, :fg_color, :bg_color, :style_mask]
+ STATE_PARAMS = [:offset, :n_open_tags, :fg_color, :bg_color, :style_mask].freeze
def convert(raw, new_state)
reset_state
@@ -126,7 +170,7 @@ module Ci
# We are only interested in color and text style changes - triggered by
# sequences starting with '\e[' and ending with 'm'. Any other control
# sequence gets stripped (including stuff like "delete last line")
- return unless indicator == '[' and terminator == 'm'
+ return unless indicator == '[' && terminator == 'm'
close_open_tags()
diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb
index 8b939663ffd..0e17ac24d5a 100644
--- a/lib/ci/api/builds.rb
+++ b/lib/ci/api/builds.rb
@@ -167,7 +167,10 @@ module Ci
build.artifacts_file = artifacts
build.artifacts_metadata = metadata
- build.artifacts_expire_in = params['expire_in']
+ build.artifacts_expire_in =
+ params['expire_in'] ||
+ Gitlab::CurrentSettings.current_application_settings
+ .default_artifacts_expire_in
if build.save
present(build, with: Entities::BuildDetails)
diff --git a/lib/ci/api/helpers.rb b/lib/ci/api/helpers.rb
index 5ff25a3a9b2..996990b464f 100644
--- a/lib/ci/api/helpers.rb
+++ b/lib/ci/api/helpers.rb
@@ -1,7 +1,7 @@
module Ci
module API
module Helpers
- BUILD_TOKEN_HEADER = "HTTP_BUILD_TOKEN"
+ BUILD_TOKEN_HEADER = "HTTP_BUILD_TOKEN".freeze
BUILD_TOKEN_PARAM = :token
UPDATE_RUNNER_EVERY = 10 * 60
@@ -60,7 +60,7 @@ module Ci
end
def build_not_found!
- if headers['User-Agent'].to_s.match(/gitlab-ci-multi-runner \d+\.\d+\.\d+(~beta\.\d+\.g[0-9a-f]+)? /)
+ if headers['User-Agent'].to_s =~ /gitlab-ci-multi-runner \d+\.\d+\.\d+(~beta\.\d+\.g[0-9a-f]+)? /
no_content!
else
not_found!
@@ -73,7 +73,7 @@ module Ci
def get_runner_version_from_params
return unless params["info"].present?
- attributes_for_keys(["name", "version", "revision", "platform", "architecture"], params["info"])
+ attributes_for_keys(%w(name version revision platform architecture), params["info"])
end
def max_artifacts_size
diff --git a/lib/ci/api/runners.rb b/lib/ci/api/runners.rb
index bcc82969eb3..2a611a67eaf 100644
--- a/lib/ci/api/runners.rb
+++ b/lib/ci/api/runners.rb
@@ -1,44 +1,36 @@
module Ci
module API
- # Runners API
class Runners < Grape::API
resource :runners do
- # Delete runner
- # Parameters:
- # token (required) - The unique token of runner
- #
- # Example Request:
- # GET /runners/delete
+ desc 'Delete a runner'
+ params do
+ requires :token, type: String, desc: 'The unique token of the runner'
+ end
delete "delete" do
- required_attributes! [:token]
authenticate_runner!
Ci::Runner.find_by_token(params[:token]).destroy
end
- # Register a new runner
- #
- # Note: This is an "internal" API called when setting up
- # runners, so it is authenticated differently.
- #
- # Parameters:
- # token (required) - The unique token of runner
- #
- # Example Request:
- # POST /runners/register
+ desc 'Register a new runner' do
+ success Entities::Runner
+ end
+ params do
+ requires :token, type: String, desc: 'The unique token of the runner'
+ optional :description, type: String, desc: 'The description of the runner'
+ optional :tag_list, type: Array[String], desc: 'A list of tags the runner should run for'
+ optional :run_untagged, type: Boolean, desc: 'Flag if the runner should execute untagged jobs'
+ optional :locked, type: Boolean, desc: 'Lock this runner for this specific project'
+ end
post "register" do
- required_attributes! [:token]
-
- attributes = attributes_for_keys(
- [:description, :tag_list, :run_untagged, :locked]
- )
+ runner_params = declared(params, include_missing: false)
runner =
if runner_registration_token_valid?
# Create shared runner. Requires admin access
- Ci::Runner.create(attributes.merge(is_shared: true))
- elsif project = Project.find_by(runners_token: params[:token])
+ Ci::Runner.create(runner_params.merge(is_shared: true))
+ elsif project = Project.find_by(runners_token: runner_params[:token])
# Create a specific runner for project.
- project.runners.create(attributes)
+ project.runners.create(runner_params)
end
return forbidden! unless runner
diff --git a/lib/ci/api/triggers.rb b/lib/ci/api/triggers.rb
index 63b42113513..6e622601680 100644
--- a/lib/ci/api/triggers.rb
+++ b/lib/ci/api/triggers.rb
@@ -1,41 +1,30 @@
module Ci
module API
- # Build Trigger API
class Triggers < Grape::API
resource :projects do
- # Trigger a GitLab CI project build
- #
- # Parameters:
- # id (required) - The ID of a CI project
- # ref (required) - The name of project's branch or tag
- # token (required) - The uniq token of trigger
- # Example Request:
- # POST /projects/:id/ref/:ref/trigger
+ desc 'Trigger a GitLab CI project build' do
+ success Entities::TriggerRequest
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of a CI project'
+ requires :ref, type: String, desc: "The name of project's branch or tag"
+ requires :token, type: String, desc: 'The unique token of the trigger'
+ optional :variables, type: Hash, desc: 'Optional build variables'
+ end
post ":id/refs/:ref/trigger" do
- required_attributes! [:token]
-
- project = Project.find_by(ci_id: params[:id].to_i)
- trigger = Ci::Trigger.find_by_token(params[:token].to_s)
+ project = Project.find_by(ci_id: params[:id])
+ trigger = Ci::Trigger.find_by_token(params[:token])
not_found! unless project && trigger
unauthorized! unless trigger.project == project
- # validate variables
- variables = params[:variables]
- if variables
- unless variables.is_a?(Hash)
- render_api_error!('variables needs to be a hash', 400)
- end
-
- unless variables.all? { |key, value| key.is_a?(String) && value.is_a?(String) }
- render_api_error!('variables needs to be a map of key-valued strings', 400)
- end
-
- # convert variables from Mash to Hash
- variables = variables.to_h
+ # Validate variables
+ variables = params[:variables].to_h
+ unless variables.all? { |key, value| key.is_a?(String) && value.is_a?(String) }
+ render_api_error!('variables needs to be a map of key-valued strings', 400)
end
# create request and trigger builds
- trigger_request = Ci::CreateTriggerRequestService.new.execute(project, trigger, params[:ref].to_s, variables)
+ trigger_request = Ci::CreateTriggerRequestService.new.execute(project, trigger, params[:ref], variables)
if trigger_request
present trigger_request, with: Entities::TriggerRequest
else
diff --git a/lib/container_registry/client.rb b/lib/container_registry/client.rb
index 2edddb84fc3..7f5f6d9ddb6 100644
--- a/lib/container_registry/client.rb
+++ b/lib/container_registry/client.rb
@@ -5,7 +5,7 @@ module ContainerRegistry
class Client
attr_accessor :uri
- MANIFEST_VERSION = 'application/vnd.docker.distribution.manifest.v2+json'
+ MANIFEST_VERSION = 'application/vnd.docker.distribution.manifest.v2+json'.freeze
# Taken from: FaradayMiddleware::FollowRedirects
REDIRECT_CODES = Set.new [301, 302, 303, 307]
diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb
index 82551f1f222..9ece84cc469 100644
--- a/lib/extracts_path.rb
+++ b/lib/extracts_path.rb
@@ -42,7 +42,7 @@ module ExtractsPath
return pair unless @project
- if id.match(/^([[:alnum:]]{40})(.+)/)
+ if id =~ /^(\h{40})(.+)/
# If the ref appears to be a SHA, we're done, just split the string
pair = $~.captures
else
diff --git a/lib/file_size_validator.rb b/lib/file_size_validator.rb
index 440dd44ece7..eb19ab45ac3 100644
--- a/lib/file_size_validator.rb
+++ b/lib/file_size_validator.rb
@@ -32,9 +32,9 @@ class FileSizeValidator < ActiveModel::EachValidator
end
def validate_each(record, attribute, value)
- raise(ArgumentError, "A CarrierWave::Uploader::Base object was expected") unless value.kind_of? CarrierWave::Uploader::Base
+ raise(ArgumentError, "A CarrierWave::Uploader::Base object was expected") unless value.is_a? CarrierWave::Uploader::Base
- value = (options[:tokenizer] || DEFAULT_TOKENIZER).call(value) if value.kind_of?(String)
+ value = (options[:tokenizer] || DEFAULT_TOKENIZER).call(value) if value.is_a?(String)
CHECKS.each do |key, validity_check|
next unless check_value = options[key]
diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb
index 9b484a2ecfd..3b210eeda9d 100644
--- a/lib/gitlab/access.rb
+++ b/lib/gitlab/access.rb
@@ -21,9 +21,7 @@ module Gitlab
PROTECTION_DEV_CAN_MERGE = 3
class << self
- def values
- options.values
- end
+ delegate :values, to: :options
def all_values
options_with_owner.values
diff --git a/lib/gitlab/asciidoc.rb b/lib/gitlab/asciidoc.rb
index 0618107e2c3..d575367d81a 100644
--- a/lib/gitlab/asciidoc.rb
+++ b/lib/gitlab/asciidoc.rb
@@ -36,6 +36,9 @@ module Gitlab
html = Banzai.post_process(html, context)
+ filter = Banzai::Filter::SanitizationFilter.new(html)
+ html = filter.call.to_s
+
html.html_safe
end
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index f638905a1e0..89db6c3da46 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -2,8 +2,8 @@ module Gitlab
module Auth
class MissingPersonalTokenError < StandardError; end
- SCOPES = [:api, :read_user]
- DEFAULT_SCOPES = [:api]
+ SCOPES = [:api, :read_user].freeze
+ DEFAULT_SCOPES = [:api].freeze
OPTIONAL_SCOPES = SCOPES - DEFAULT_SCOPES
class << self
diff --git a/lib/gitlab/award_emoji.rb b/lib/gitlab/award_emoji.rb
index 39b43ab5489..7555326d384 100644
--- a/lib/gitlab/award_emoji.rb
+++ b/lib/gitlab/award_emoji.rb
@@ -69,11 +69,12 @@ module Gitlab
end
JSON.parse(File.read(path)).map do |hash|
- if digest
- fname = "#{hash['unicode']}-#{hash['digest']}"
- else
- fname = hash['unicode']
- end
+ fname =
+ if digest
+ "#{hash['unicode']}-#{hash['digest']}"
+ else
+ hash['unicode']
+ end
{ name: hash['name'], path: File.join(base, prefix, "#{fname}.png") }
end
diff --git a/lib/gitlab/badge/build/template.rb b/lib/gitlab/badge/build/template.rb
index 2b95ddfcb53..bc0e0cd441d 100644
--- a/lib/gitlab/badge/build/template.rb
+++ b/lib/gitlab/badge/build/template.rb
@@ -15,7 +15,7 @@ module Gitlab
canceled: '#9f9f9f',
skipped: '#9f9f9f',
unknown: '#9f9f9f'
- }
+ }.freeze
def initialize(badge)
@entity = badge.entity
diff --git a/lib/gitlab/badge/coverage/template.rb b/lib/gitlab/badge/coverage/template.rb
index 06e0d084e9f..fcecb1d9665 100644
--- a/lib/gitlab/badge/coverage/template.rb
+++ b/lib/gitlab/badge/coverage/template.rb
@@ -13,7 +13,7 @@ module Gitlab
medium: '#dfb317',
low: '#e05d44',
unknown: '#9f9f9f'
- }
+ }.freeze
def initialize(badge)
@entity = badge.entity
diff --git a/lib/gitlab/badge/metadata.rb b/lib/gitlab/badge/metadata.rb
index 548f85b78bb..4a049ef758d 100644
--- a/lib/gitlab/badge/metadata.rb
+++ b/lib/gitlab/badge/metadata.rb
@@ -20,6 +20,10 @@ module Gitlab
"[![#{title}](#{image_url})](#{link_url})"
end
+ def to_asciidoc
+ "image:#{image_url}[link=\"#{link_url}\",title=\"#{title}\"]"
+ end
+
def title
raise NotImplementedError
end
diff --git a/lib/gitlab/changes_list.rb b/lib/gitlab/changes_list.rb
index 95308aca95f..5b32fca00a4 100644
--- a/lib/gitlab/changes_list.rb
+++ b/lib/gitlab/changes_list.rb
@@ -5,7 +5,7 @@ module Gitlab
attr_reader :raw_changes
def initialize(changes)
- @raw_changes = changes.kind_of?(String) ? changes.lines : changes
+ @raw_changes = changes.is_a?(String) ? changes.lines : changes
end
def each(&block)
diff --git a/lib/gitlab/chat_commands/presenters/issuable.rb b/lib/gitlab/chat_commands/presenters/issue_base.rb
index dfb1c8f6616..054f7f4be0c 100644
--- a/lib/gitlab/chat_commands/presenters/issuable.rb
+++ b/lib/gitlab/chat_commands/presenters/issue_base.rb
@@ -1,7 +1,7 @@
module Gitlab
module ChatCommands
module Presenters
- module Issuable
+ module IssueBase
def color(issuable)
issuable.open? ? '#38ae67' : '#d22852'
end
@@ -32,7 +32,7 @@ module Gitlab
},
{
title: "Labels",
- value: @resource.labels.any? ? @resource.label_names : "_None_",
+ value: @resource.labels.any? ? @resource.label_names.join(', ') : "_None_",
short: true
}
]
diff --git a/lib/gitlab/chat_commands/presenters/issue_new.rb b/lib/gitlab/chat_commands/presenters/issue_new.rb
index a1a3add56c9..3674ba25641 100644
--- a/lib/gitlab/chat_commands/presenters/issue_new.rb
+++ b/lib/gitlab/chat_commands/presenters/issue_new.rb
@@ -2,7 +2,7 @@ module Gitlab
module ChatCommands
module Presenters
class IssueNew < Presenters::Base
- include Presenters::Issuable
+ include Presenters::IssueBase
def present
in_channel_response(new_issue)
@@ -10,7 +10,7 @@ module Gitlab
private
- def new_issue
+ def new_issue
{
attachments: [
{
@@ -38,7 +38,7 @@ module Gitlab
end
def project_link
- "[#{project.name_with_namespace}](#{projects_url(project)})"
+ "[#{project.name_with_namespace}](#{project.web_url})"
end
def author_profile_link
diff --git a/lib/gitlab/chat_commands/presenters/issue_search.rb b/lib/gitlab/chat_commands/presenters/issue_search.rb
index 3478359b91d..73788cf9662 100644
--- a/lib/gitlab/chat_commands/presenters/issue_search.rb
+++ b/lib/gitlab/chat_commands/presenters/issue_search.rb
@@ -2,7 +2,7 @@ module Gitlab
module ChatCommands
module Presenters
class IssueSearch < Presenters::Base
- include Presenters::Issuable
+ include Presenters::IssueBase
def present
text = if @resource.count >= 5
diff --git a/lib/gitlab/chat_commands/presenters/issue_show.rb b/lib/gitlab/chat_commands/presenters/issue_show.rb
index fe5847ccd15..bd784ad241e 100644
--- a/lib/gitlab/chat_commands/presenters/issue_show.rb
+++ b/lib/gitlab/chat_commands/presenters/issue_show.rb
@@ -2,7 +2,7 @@ module Gitlab
module ChatCommands
module Presenters
class IssueShow < Presenters::Base
- include Presenters::Issuable
+ include Presenters::IssueBase
def present
if @resource.confidential?
diff --git a/lib/gitlab/ci/build/artifacts/metadata/entry.rb b/lib/gitlab/ci/build/artifacts/metadata/entry.rb
index 7f4c750b6fd..6f799c2f031 100644
--- a/lib/gitlab/ci/build/artifacts/metadata/entry.rb
+++ b/lib/gitlab/ci/build/artifacts/metadata/entry.rb
@@ -27,6 +27,8 @@ module Gitlab
end
end
+ delegate :empty?, to: :children
+
def directory?
blank_node? || @path.end_with?('/')
end
@@ -91,10 +93,6 @@ module Gitlab
blank_node? || @entries.include?(@path)
end
- def empty?
- children.empty?
- end
-
def total_size
descendant_pattern = %r{^#{Regexp.escape(@path)}}
entries.sum do |path, entry|
diff --git a/lib/gitlab/ci/config/entry/artifacts.rb b/lib/gitlab/ci/config/entry/artifacts.rb
index b756b0d4555..8275aacee9b 100644
--- a/lib/gitlab/ci/config/entry/artifacts.rb
+++ b/lib/gitlab/ci/config/entry/artifacts.rb
@@ -9,7 +9,7 @@ module Gitlab
include Validatable
include Attributable
- ALLOWED_KEYS = %i[name untracked paths when expire_in]
+ ALLOWED_KEYS = %i[name untracked paths when expire_in].freeze
attributes ALLOWED_KEYS
diff --git a/lib/gitlab/ci/config/entry/cache.rb b/lib/gitlab/ci/config/entry/cache.rb
index 7653cab668b..066643ccfcc 100644
--- a/lib/gitlab/ci/config/entry/cache.rb
+++ b/lib/gitlab/ci/config/entry/cache.rb
@@ -8,7 +8,7 @@ module Gitlab
class Cache < Node
include Configurable
- ALLOWED_KEYS = %i[key untracked paths]
+ ALLOWED_KEYS = %i[key untracked paths].freeze
validations do
validates :config, allowed_keys: ALLOWED_KEYS
diff --git a/lib/gitlab/ci/config/entry/configurable.rb b/lib/gitlab/ci/config/entry/configurable.rb
index 833ae4a0ff3..e05aca9881b 100644
--- a/lib/gitlab/ci/config/entry/configurable.rb
+++ b/lib/gitlab/ci/config/entry/configurable.rb
@@ -58,7 +58,7 @@ module Gitlab
def helpers(*nodes)
nodes.each do |symbol|
define_method("#{symbol}_defined?") do
- @entries[symbol].specified? if @entries[symbol]
+ @entries[symbol]&.specified?
end
define_method("#{symbol}_value") do
diff --git a/lib/gitlab/ci/config/entry/environment.rb b/lib/gitlab/ci/config/entry/environment.rb
index f7c530c7d9f..0c1f9eb7cbf 100644
--- a/lib/gitlab/ci/config/entry/environment.rb
+++ b/lib/gitlab/ci/config/entry/environment.rb
@@ -8,7 +8,7 @@ module Gitlab
class Environment < Node
include Validatable
- ALLOWED_KEYS = %i[name url action on_stop]
+ ALLOWED_KEYS = %i[name url action on_stop].freeze
validations do
validate do
@@ -21,12 +21,14 @@ module Gitlab
validates :name,
type: {
with: String,
- message: Gitlab::Regex.environment_name_regex_message }
+ message: Gitlab::Regex.environment_name_regex_message
+ }
validates :name,
format: {
with: Gitlab::Regex.environment_name_regex,
- message: Gitlab::Regex.environment_name_regex_message }
+ message: Gitlab::Regex.environment_name_regex_message
+ }
with_options if: :hash? do
validates :config, allowed_keys: ALLOWED_KEYS
diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb
index 69a5e6f433d..7f7662f2776 100644
--- a/lib/gitlab/ci/config/entry/job.rb
+++ b/lib/gitlab/ci/config/entry/job.rb
@@ -11,7 +11,7 @@ module Gitlab
ALLOWED_KEYS = %i[tags script only except type image services allow_failure
type stage when artifacts cache dependencies before_script
- after_script variables environment coverage]
+ after_script variables environment coverage].freeze
validations do
validates :config, allowed_keys: ALLOWED_KEYS
diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb
index c843315782d..d80bc748209 100644
--- a/lib/gitlab/conflict/file.rb
+++ b/lib/gitlab/conflict/file.rb
@@ -91,11 +91,12 @@ module Gitlab
our_highlight = Gitlab::Highlight.highlight(our_path, our_file, repository: repository).lines
lines.each do |line|
- if line.type == 'old'
- line.rich_text = their_highlight[line.old_line - 1].try(:html_safe)
- else
- line.rich_text = our_highlight[line.new_line - 1].try(:html_safe)
- end
+ line.rich_text =
+ if line.type == 'old'
+ their_highlight[line.old_line - 1].try(:html_safe)
+ else
+ our_highlight[line.new_line - 1].try(:html_safe)
+ end
end
end
diff --git a/lib/gitlab/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb
index 7e3d5647b39..15992b77680 100644
--- a/lib/gitlab/contributions_calendar.rb
+++ b/lib/gitlab/contributions_calendar.rb
@@ -22,8 +22,10 @@ module Gitlab
having(action: [Event::CREATED, Event::CLOSED], target_type: "Issue")
mr_events = event_counts(date_from, :merge_requests).
having(action: [Event::MERGED, Event::CREATED, Event::CLOSED], target_type: "MergeRequest")
+ note_events = event_counts(date_from, :merge_requests).
+ having(action: [Event::COMMENTED], target_type: "Note")
- union = Gitlab::SQL::Union.new([repo_events, issue_events, mr_events])
+ union = Gitlab::SQL::Union.new([repo_events, issue_events, mr_events, note_events])
events = Event.find_by_sql(union.to_sql).map(&:attributes)
@activity_events = events.each_with_object(Hash.new {|h, k| h[k] = 0 }) do |event, activities|
@@ -38,7 +40,7 @@ module Gitlab
# Use visible_to_user? instead of the complicated logic in activity_dates
# because we're only viewing the events for a single day.
- events.select {|event| event.visible_to_user?(current_user) }
+ events.select { |event| event.visible_to_user?(current_user) }
end
def starting_year
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index e20f5f6f514..82576d197fe 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -25,9 +25,7 @@ module Gitlab
settings || in_memory_application_settings
end
- def sidekiq_throttling_enabled?
- current_application_settings.sidekiq_throttling_enabled?
- end
+ delegate :sidekiq_throttling_enabled?, to: :current_application_settings
def in_memory_application_settings
@in_memory_application_settings ||= ::ApplicationSetting.new(::ApplicationSetting.defaults)
diff --git a/lib/gitlab/cycle_analytics/base_event_fetcher.rb b/lib/gitlab/cycle_analytics/base_event_fetcher.rb
index 0d8791d396b..ab115afcaa5 100644
--- a/lib/gitlab/cycle_analytics/base_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/base_event_fetcher.rb
@@ -5,6 +5,8 @@ module Gitlab
attr_reader :projections, :query, :stage, :order
+ MAX_EVENTS = 50
+
def initialize(project:, stage:, options:)
@project = project
@stage = stage
@@ -38,7 +40,7 @@ module Gitlab
def events_query
diff_fn = subtract_datetimes_diff(base_query, @options[:start_time_attrs], @options[:end_time_attrs])
- base_query.project(extract_diff_epoch(diff_fn).as('total_time'), *projections).order(order.desc)
+ base_query.project(extract_diff_epoch(diff_fn).as('total_time'), *projections).order(order.desc).take(MAX_EVENTS)
end
def default_order
diff --git a/lib/gitlab/cycle_analytics/code_stage.rb b/lib/gitlab/cycle_analytics/code_stage.rb
index d1bc2055ba8..1e52b6614a1 100644
--- a/lib/gitlab/cycle_analytics/code_stage.rb
+++ b/lib/gitlab/cycle_analytics/code_stage.rb
@@ -13,6 +13,10 @@ module Gitlab
:code
end
+ def legend
+ "Related Merge Requests"
+ end
+
def description
"Time until first merge request"
end
diff --git a/lib/gitlab/cycle_analytics/issue_stage.rb b/lib/gitlab/cycle_analytics/issue_stage.rb
index d2068fbc38f..213994988a5 100644
--- a/lib/gitlab/cycle_analytics/issue_stage.rb
+++ b/lib/gitlab/cycle_analytics/issue_stage.rb
@@ -14,6 +14,10 @@ module Gitlab
:issue
end
+ def legend
+ "Related Issues"
+ end
+
def description
"Time before an issue gets scheduled"
end
diff --git a/lib/gitlab/cycle_analytics/plan_stage.rb b/lib/gitlab/cycle_analytics/plan_stage.rb
index 3b4dfc6a30e..45d51d30ccc 100644
--- a/lib/gitlab/cycle_analytics/plan_stage.rb
+++ b/lib/gitlab/cycle_analytics/plan_stage.rb
@@ -14,6 +14,10 @@ module Gitlab
:plan
end
+ def legend
+ "Related Commits"
+ end
+
def description
"Time before an issue starts implementation"
end
diff --git a/lib/gitlab/cycle_analytics/production_stage.rb b/lib/gitlab/cycle_analytics/production_stage.rb
index 2a6bcc80116..9f387a02945 100644
--- a/lib/gitlab/cycle_analytics/production_stage.rb
+++ b/lib/gitlab/cycle_analytics/production_stage.rb
@@ -15,6 +15,10 @@ module Gitlab
:production
end
+ def legend
+ "Related Issues"
+ end
+
def description
"From issue creation until deploy to production"
end
diff --git a/lib/gitlab/cycle_analytics/review_stage.rb b/lib/gitlab/cycle_analytics/review_stage.rb
index fbaa3010d81..4744be834de 100644
--- a/lib/gitlab/cycle_analytics/review_stage.rb
+++ b/lib/gitlab/cycle_analytics/review_stage.rb
@@ -13,6 +13,10 @@ module Gitlab
:review
end
+ def legend
+ "Relative Merged Requests"
+ end
+
def description
"Time between merge request creation and merge/close"
end
diff --git a/lib/gitlab/cycle_analytics/staging_stage.rb b/lib/gitlab/cycle_analytics/staging_stage.rb
index 945909a4d62..3cdbe04fbaf 100644
--- a/lib/gitlab/cycle_analytics/staging_stage.rb
+++ b/lib/gitlab/cycle_analytics/staging_stage.rb
@@ -14,6 +14,10 @@ module Gitlab
:staging
end
+ def legend
+ "Relative Deployed Builds"
+ end
+
def description
"From merge request merge until deploy to production"
end
diff --git a/lib/gitlab/cycle_analytics/test_stage.rb b/lib/gitlab/cycle_analytics/test_stage.rb
index 0079d56e0e4..e96943833bc 100644
--- a/lib/gitlab/cycle_analytics/test_stage.rb
+++ b/lib/gitlab/cycle_analytics/test_stage.rb
@@ -13,6 +13,10 @@ module Gitlab
:test
end
+ def legend
+ "Relative Builds Trigger by Commits"
+ end
+
def description
"Total test time for all commits/merges"
end
diff --git a/lib/gitlab/data_builder/build.rb b/lib/gitlab/data_builder/build.rb
index 6548e6475c6..f78106f5b10 100644
--- a/lib/gitlab/data_builder/build.rb
+++ b/lib/gitlab/data_builder/build.rb
@@ -8,6 +8,8 @@ module Gitlab
commit = build.pipeline
user = build.user
+ author_url = build_author_url(build.commit, commit)
+
data = {
object_kind: 'build',
@@ -43,6 +45,7 @@ module Gitlab
message: commit.git_commit_message,
author_name: commit.git_author_name,
author_email: commit.git_author_email,
+ author_url: author_url,
status: commit.status,
duration: commit.duration,
started_at: commit.started_at,
@@ -62,6 +65,13 @@ module Gitlab
data
end
+
+ private
+
+ def build_author_url(commit, pipeline)
+ author = commit.try(:author)
+ author ? Gitlab::Routing.url_helpers.user_url(author) : "mailto:#{pipeline.git_author_email}"
+ end
end
end
end
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index dc2537d36aa..d160cadc2d0 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -6,7 +6,7 @@ module Gitlab
MAX_INT_VALUE = 2147483647
def self.adapter_name
- connection.adapter_name
+ ActiveRecord::Base.configurations[Rails.env]['adapter']
end
def self.mysql?
@@ -69,6 +69,36 @@ module Gitlab
end
end
+ def self.with_connection_pool(pool_size)
+ pool = create_connection_pool(pool_size)
+
+ begin
+ yield(pool)
+ ensure
+ pool.disconnect!
+ end
+ end
+
+ # pool_size - The size of the DB pool.
+ # host - An optional host name to use instead of the default one.
+ def self.create_connection_pool(pool_size, host = nil)
+ # See activerecord-4.2.7.1/lib/active_record/connection_adapters/connection_specification.rb
+ env = Rails.env
+ original_config = ActiveRecord::Base.configurations
+
+ env_config = original_config[env].merge('pool' => pool_size)
+ env_config['host'] = host if host
+
+ config = original_config.merge(env => env_config)
+
+ spec =
+ ActiveRecord::
+ ConnectionAdapters::
+ ConnectionSpecification::Resolver.new(config).spec(env.to_sym)
+
+ ActiveRecord::ConnectionAdapters::ConnectionPool.new(spec)
+ end
+
def self.connection
ActiveRecord::Base.connection
end
diff --git a/lib/gitlab/database/median.rb b/lib/gitlab/database/median.rb
index 08607c27c09..23890e5f493 100644
--- a/lib/gitlab/database/median.rb
+++ b/lib/gitlab/database/median.rb
@@ -108,6 +108,7 @@ module Gitlab
Arel.sql(%Q{EXTRACT(EPOCH FROM (#{diff.to_sql}))})
end
+
# Need to cast '0' to an INTERVAL before we can check if the interval is positive
def zero_interval
Arel::Nodes::NamedFunction.new("CAST", [Arel.sql("'0' AS INTERVAL")])
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 0bd6e148ba8..fc445ab9483 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -26,11 +26,68 @@ module Gitlab
add_index(table_name, column_name, options)
end
+ # Adds a foreign key with only minimal locking on the tables involved.
+ #
+ # This method only requires minimal locking when using PostgreSQL. When
+ # using MySQL this method will use Rails' default `add_foreign_key`.
+ #
+ # source - The source table containing the foreign key.
+ # target - The target table the key points to.
+ # column - The name of the column to create the foreign key on.
+ # on_delete - The action to perform when associated data is removed,
+ # defaults to "CASCADE".
+ def add_concurrent_foreign_key(source, target, column:, on_delete: :cascade)
+ # Transactions would result in ALTER TABLE locks being held for the
+ # duration of the transaction, defeating the purpose of this method.
+ if transaction_open?
+ raise 'add_concurrent_foreign_key can not be run inside a transaction'
+ end
+
+ # While MySQL does allow disabling of foreign keys it has no equivalent
+ # of PostgreSQL's "VALIDATE CONSTRAINT". As a result we'll just fall
+ # back to the normal foreign key procedure.
+ if Database.mysql?
+ return add_foreign_key(source, target,
+ column: column,
+ on_delete: on_delete)
+ end
+
+ disable_statement_timeout
+
+ key_name = concurrent_foreign_key_name(source, column)
+
+ # Using NOT VALID allows us to create a key without immediately
+ # validating it. This means we keep the ALTER TABLE lock only for a
+ # short period of time. The key _is_ enforced for any newly created
+ # data.
+ execute <<-EOF.strip_heredoc
+ ALTER TABLE #{source}
+ ADD CONSTRAINT #{key_name}
+ FOREIGN KEY (#{column})
+ REFERENCES #{target} (id)
+ ON DELETE #{on_delete} NOT VALID;
+ EOF
+
+ # Validate the existing constraint. This can potentially take a very
+ # long time to complete, but fortunately does not lock the source table
+ # while running.
+ execute("ALTER TABLE #{source} VALIDATE CONSTRAINT #{key_name};")
+ end
+
+ # Returns the name for a concurrent foreign key.
+ #
+ # PostgreSQL constraint names have a limit of 63 bytes. The logic used
+ # here is based on Rails' foreign_key_name() method, which unfortunately
+ # is private so we can't rely on it directly.
+ def concurrent_foreign_key_name(table, column)
+ "fk_#{Digest::SHA256.hexdigest("#{table}_#{column}_fk").first(10)}"
+ end
+
# Long-running migrations may take more than the timeout allowed by
# the database. Disable the session's statement timeout to ensure
# migrations don't get killed prematurely. (PostgreSQL only)
def disable_statement_timeout
- ActiveRecord::Base.connection.execute('SET statement_timeout TO 0') if Database.postgresql?
+ execute('SET statement_timeout TO 0') if Database.postgresql?
end
# Updates the value of a column in batches.
diff --git a/lib/gitlab/diff/highlight.rb b/lib/gitlab/diff/highlight.rb
index 9ea976e18fa..7db896522a9 100644
--- a/lib/gitlab/diff/highlight.rb
+++ b/lib/gitlab/diff/highlight.rb
@@ -50,7 +50,7 @@ module Gitlab
# Only update text if line is found. This will prevent
# issues with submodules given the line only exists in diff content.
if rich_line
- line_prefix = diff_line.text.match(/\A(.)/) ? $1 : ' '
+ line_prefix = diff_line.text =~ /\A(.)/ ? $1 : ' '
"#{line_prefix}#{rich_line}".html_safe
end
end
diff --git a/lib/gitlab/diff/inline_diff_marker.rb b/lib/gitlab/diff/inline_diff_marker.rb
index 87a9b1e23ac..736933b1c4b 100644
--- a/lib/gitlab/diff/inline_diff_marker.rb
+++ b/lib/gitlab/diff/inline_diff_marker.rb
@@ -4,7 +4,7 @@ module Gitlab
MARKDOWN_SYMBOLS = {
addition: "+",
deletion: "-"
- }
+ }.freeze
attr_accessor :raw_line, :rich_line
diff --git a/lib/gitlab/diff/parser.rb b/lib/gitlab/diff/parser.rb
index 59a2367b65d..8f844224a7a 100644
--- a/lib/gitlab/diff/parser.rb
+++ b/lib/gitlab/diff/parser.rb
@@ -20,7 +20,7 @@ module Gitlab
full_line = line.delete("\n")
- if line.match(/^@@ -/)
+ if line =~ /^@@ -/
type = "match"
line_old = line.match(/\-[0-9]*/)[0].to_i.abs rescue 0
@@ -45,7 +45,7 @@ module Gitlab
line_new += 1
when "-"
line_old += 1
- when "\\"
+ when "\\" # rubocop:disable Lint/EmptyWhen
# No increment
else
line_new += 1
diff --git a/lib/gitlab/diff/position.rb b/lib/gitlab/diff/position.rb
index ecf62dead35..fc728123c97 100644
--- a/lib/gitlab/diff/position.rb
+++ b/lib/gitlab/diff/position.rb
@@ -140,15 +140,16 @@ module Gitlab
def find_diff_file(repository)
# We're at the initial commit, so just get that as we can't compare to anything.
- if Gitlab::Git.blank_ref?(start_sha)
- compare = Gitlab::Git::Commit.find(repository.raw_repository, head_sha)
- else
- compare = Gitlab::Git::Compare.new(
- repository.raw_repository,
- start_sha,
- head_sha
- )
- end
+ compare =
+ if Gitlab::Git.blank_ref?(start_sha)
+ Gitlab::Git::Commit.find(repository.raw_repository, head_sha)
+ else
+ Gitlab::Git::Compare.new(
+ repository.raw_repository,
+ start_sha,
+ head_sha
+ )
+ end
diff = compare.diffs(paths: paths).first
diff --git a/lib/gitlab/downtime_check/message.rb b/lib/gitlab/downtime_check/message.rb
index 40a4815a9a0..543e62794c5 100644
--- a/lib/gitlab/downtime_check/message.rb
+++ b/lib/gitlab/downtime_check/message.rb
@@ -3,8 +3,8 @@ module Gitlab
class Message
attr_reader :path, :offline
- OFFLINE = "\e[31moffline\e[0m"
- ONLINE = "\e[32monline\e[0m"
+ OFFLINE = "\e[31moffline\e[0m".freeze
+ ONLINE = "\e[32monline\e[0m".freeze
# path - The file path of the migration.
# offline - When set to `true` the migration will require downtime.
diff --git a/lib/gitlab/ee_compat_check.rb b/lib/gitlab/ee_compat_check.rb
index c8e36d8ff4a..e0fdf3f3d64 100644
--- a/lib/gitlab/ee_compat_check.rb
+++ b/lib/gitlab/ee_compat_check.rb
@@ -119,7 +119,7 @@ module Gitlab
step("Reseting to latest master", %w[git reset --hard origin/master])
step("Checking if #{patch_path} applies cleanly to EE/master")
- output, status = Gitlab::Popen.popen(%W[git apply --check #{patch_path}])
+ output, status = Gitlab::Popen.popen(%W[git apply --check --3way #{patch_path}])
unless status.zero?
failed_files = output.lines.reduce([]) do |memo, line|
diff --git a/lib/gitlab/email/handler.rb b/lib/gitlab/email/handler.rb
index bd2f5d3615e..35ea2e0ef59 100644
--- a/lib/gitlab/email/handler.rb
+++ b/lib/gitlab/email/handler.rb
@@ -5,7 +5,7 @@ require 'gitlab/email/handler/unsubscribe_handler'
module Gitlab
module Email
module Handler
- HANDLERS = [UnsubscribeHandler, CreateNoteHandler, CreateIssueHandler]
+ HANDLERS = [UnsubscribeHandler, CreateNoteHandler, CreateIssueHandler].freeze
def self.for(mail, mail_key)
HANDLERS.find do |klass|
diff --git a/lib/gitlab/email/message/repository_push.rb b/lib/gitlab/email/message/repository_push.rb
index 0e3b65fceb4..6c69cd9e6a9 100644
--- a/lib/gitlab/email/message/repository_push.rb
+++ b/lib/gitlab/email/message/repository_push.rb
@@ -46,7 +46,7 @@ module Gitlab
end
def diffs_count
- diffs.size if diffs
+ diffs&.size
end
def compare
@@ -58,7 +58,7 @@ module Gitlab
end
def compare_timeout
- diffs.overflow? if diffs
+ diffs&.overflow?
end
def reverse_compare?
diff --git a/lib/gitlab/email/reply_parser.rb b/lib/gitlab/email/reply_parser.rb
index 8c8dd1b9cef..558df87f36d 100644
--- a/lib/gitlab/email/reply_parser.rb
+++ b/lib/gitlab/email/reply_parser.rb
@@ -31,11 +31,12 @@ module Gitlab
private
def select_body(message)
- if message.multipart?
- part = message.text_part || message.html_part || message
- else
- part = message
- end
+ part =
+ if message.multipart?
+ message.text_part || message.html_part || message
+ else
+ message
+ end
decoded = fix_charset(part)
diff --git a/lib/gitlab/exclusive_lease.rb b/lib/gitlab/exclusive_lease.rb
index 2dd42704396..62ddd45785d 100644
--- a/lib/gitlab/exclusive_lease.rb
+++ b/lib/gitlab/exclusive_lease.rb
@@ -10,7 +10,7 @@ module Gitlab
# ExclusiveLease.
#
class ExclusiveLease
- LUA_CANCEL_SCRIPT = <<-EOS
+ LUA_CANCEL_SCRIPT = <<-EOS.freeze
local key, uuid = KEYS[1], ARGV[1]
if redis.call("get", key) == uuid then
redis.call("del", key)
diff --git a/lib/gitlab/file_detector.rb b/lib/gitlab/file_detector.rb
index 1d93a67dc56..c9ca4cadd1c 100644
--- a/lib/gitlab/file_detector.rb
+++ b/lib/gitlab/file_detector.rb
@@ -14,7 +14,7 @@ module Gitlab
koding: '.koding.yml',
gitlab_ci: '.gitlab-ci.yml',
avatar: /\Alogo\.(png|jpg|gif)\z/
- }
+ }.freeze
# Returns an Array of file types based on the given paths.
#
diff --git a/lib/gitlab/git/blob_snippet.rb b/lib/gitlab/git/blob_snippet.rb
index e98de57fc22..d7975f88aaa 100644
--- a/lib/gitlab/git/blob_snippet.rb
+++ b/lib/gitlab/git/blob_snippet.rb
@@ -13,7 +13,7 @@ module Gitlab
end
def data
- lines.join("\n") if lines
+ lines&.join("\n")
end
def name
diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb
index d785516ebdd..3a73697dc5d 100644
--- a/lib/gitlab/git/commit.rb
+++ b/lib/gitlab/git/commit.rb
@@ -14,6 +14,8 @@ module Gitlab
attr_accessor *SERIALIZE_KEYS # rubocop:disable Lint/AmbiguousOperator
+ delegate :tree, to: :raw_commit
+
def ==(other)
return false unless other.is_a?(Gitlab::Git::Commit)
@@ -218,10 +220,6 @@ module Gitlab
raw_commit.parents.map { |c| Gitlab::Git::Commit.new(c) }
end
- def tree
- raw_commit.tree
- end
-
def stats
Gitlab::Git::CommitStats.new(self)
end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 7068e68a855..4b6ad8037ce 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -1,5 +1,4 @@
# Gitlab::Git::Repository is a wrapper around native Rugged::Repository object
-require 'forwardable'
require 'tempfile'
require 'forwardable'
require "rubygems/package"
@@ -7,7 +6,6 @@ require "rubygems/package"
module Gitlab
module Git
class Repository
- extend Forwardable
include Gitlab::Git::Popen
SEARCH_CONTEXT_LINES = 3
@@ -33,6 +31,10 @@ module Gitlab
@attributes = Gitlab::Git::Attributes.new(path)
end
+ delegate :empty?,
+ :bare?,
+ to: :rugged
+
# Default branch in the repository
def root_ref
@root_ref ||= discover_default_branch
@@ -162,14 +164,6 @@ module Gitlab
!empty?
end
- def empty?
- rugged.empty?
- end
-
- def bare?
- rugged.bare?
- end
-
def repo_exists?
!!rugged
end
@@ -565,9 +559,7 @@ module Gitlab
# will trigger a +:mixed+ reset and the working directory will be
# replaced with the content of the index. (Untracked and ignored files
# will be left alone)
- def reset(ref, reset_type)
- rugged.reset(ref, reset_type)
- end
+ delegate :reset, to: :rugged
# Mimic the `git clean` command and recursively delete untracked files.
# Valid keys that can be passed in the +options+ hash are:
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index 7e1484613f2..ffb178334bc 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -10,10 +10,10 @@ module Gitlab
deploy_key_upload:
'This deploy key does not have write access to this project.',
no_repo: 'A repository for this project does not exist yet.'
- }
+ }.freeze
- DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive }
- PUSH_COMMANDS = %w{ git-receive-pack }
+ DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive }.freeze
+ PUSH_COMMANDS = %w{ git-receive-pack }.freeze
ALL_COMMANDS = DOWNLOAD_COMMANDS + PUSH_COMMANDS
attr_reader :actor, :project, :protocol, :user_access, :authentication_abilities
diff --git a/lib/gitlab/github_import/base_formatter.rb b/lib/gitlab/github_import/base_formatter.rb
index 95dba9a327b..8c80791e7c9 100644
--- a/lib/gitlab/github_import/base_formatter.rb
+++ b/lib/gitlab/github_import/base_formatter.rb
@@ -1,11 +1,12 @@
module Gitlab
module GithubImport
class BaseFormatter
- attr_reader :formatter, :project, :raw_data
+ attr_reader :client, :formatter, :project, :raw_data
- def initialize(project, raw_data)
+ def initialize(project, raw_data, client = nil)
@project = project
@raw_data = raw_data
+ @client = client
@formatter = Gitlab::ImportFormatter.new
end
@@ -18,19 +19,6 @@ module Gitlab
def url
raw_data.url || ''
end
-
- private
-
- def gitlab_user_id(github_id)
- User.joins(:identities).
- find_by("identities.extern_uid = ? AND identities.provider = 'github'", github_id.to_s).
- try(:id)
- end
-
- def gitlab_author_id
- return @gitlab_author_id if defined?(@gitlab_author_id)
- @gitlab_author_id = gitlab_user_id(raw_data.user.id)
- end
end
end
end
diff --git a/lib/gitlab/github_import/client.rb b/lib/gitlab/github_import/client.rb
index ba869faa92e..7dbeec5b010 100644
--- a/lib/gitlab/github_import/client.rb
+++ b/lib/gitlab/github_import/client.rb
@@ -10,6 +10,7 @@ module Gitlab
@access_token = access_token
@host = host.to_s.sub(%r{/+\z}, '')
@api_version = api_version
+ @users = {}
if access_token
::Octokit.auto_paginate = false
@@ -64,6 +65,13 @@ module Gitlab
api.respond_to?(method) || super
end
+ def user(login)
+ return nil unless login.present?
+ return @users[login] if @users.key?(login)
+
+ @users[login] = api.user(login)
+ end
+
private
def api_endpoint
diff --git a/lib/gitlab/github_import/comment_formatter.rb b/lib/gitlab/github_import/comment_formatter.rb
index 2bddcde2b7c..e21922070c1 100644
--- a/lib/gitlab/github_import/comment_formatter.rb
+++ b/lib/gitlab/github_import/comment_formatter.rb
@@ -1,6 +1,8 @@
module Gitlab
module GithubImport
class CommentFormatter < BaseFormatter
+ attr_writer :author_id
+
def attributes
{
project: project,
@@ -17,11 +19,11 @@ module Gitlab
private
def author
- raw_data.user.login
+ @author ||= UserFormatter.new(client, raw_data.user)
end
def author_id
- gitlab_author_id || project.creator_id
+ author.gitlab_id || project.creator_id
end
def body
@@ -52,10 +54,10 @@ module Gitlab
end
def note
- if gitlab_author_id
+ if author.gitlab_id
body
else
- formatter.author_line(author) + body
+ formatter.author_line(author.login) + body
end
end
diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb
index ec1318ab33c..dc73cad93a5 100644
--- a/lib/gitlab/github_import/importer.rb
+++ b/lib/gitlab/github_import/importer.rb
@@ -110,12 +110,12 @@ module Gitlab
def import_issues
fetch_resources(:issues, repo, state: :all, sort: :created, direction: :asc, per_page: 100) do |issues|
issues.each do |raw|
- gh_issue = IssueFormatter.new(project, raw)
+ gh_issue = IssueFormatter.new(project, raw, client)
begin
issuable =
if gh_issue.pull_request?
- MergeRequest.find_by_iid(gh_issue.number)
+ MergeRequest.find_by(target_project_id: project.id, iid: gh_issue.number)
else
gh_issue.create!
end
@@ -131,7 +131,8 @@ module Gitlab
def import_pull_requests
fetch_resources(:pull_requests, repo, state: :all, sort: :created, direction: :asc, per_page: 100) do |pull_requests|
pull_requests.each do |raw|
- gh_pull_request = PullRequestFormatter.new(project, raw)
+ gh_pull_request = PullRequestFormatter.new(project, raw, client)
+
next unless gh_pull_request.valid?
begin
@@ -209,11 +210,17 @@ module Gitlab
ActiveRecord::Base.no_touching do
comments.each do |raw|
begin
- comment = CommentFormatter.new(project, raw)
+ comment = CommentFormatter.new(project, raw, client)
+
# GH does not return info about comment's parent, so we guess it by checking its URL!
*_, parent, iid = URI(raw.html_url).path.split('/')
- issuable_class = parent == 'issues' ? Issue : MergeRequest
- issuable = issuable_class.find_by_iid(iid)
+
+ issuable = if parent == 'issues'
+ Issue.find_by(project_id: project.id, iid: iid)
+ else
+ MergeRequest.find_by(target_project_id: project.id, iid: iid)
+ end
+
next unless issuable
issuable.notes.create!(comment.attributes)
@@ -278,7 +285,7 @@ module Gitlab
def fetch_resources(resource_type, *opts)
return if imported?(resource_type)
- opts.last.merge!(page: current_page(resource_type))
+ opts.last[:page] = current_page(resource_type)
client.public_send(resource_type, *opts) do |resources|
yield resources
diff --git a/lib/gitlab/github_import/issuable_formatter.rb b/lib/gitlab/github_import/issuable_formatter.rb
index 256f360efc7..27b171d6ddb 100644
--- a/lib/gitlab/github_import/issuable_formatter.rb
+++ b/lib/gitlab/github_import/issuable_formatter.rb
@@ -1,13 +1,13 @@
module Gitlab
module GithubImport
class IssuableFormatter < BaseFormatter
+ attr_writer :assignee_id, :author_id
+
def project_association
raise NotImplementedError
end
- def number
- raw_data.number
- end
+ delegate :number, to: :raw_data
def find_condition
{ iid: number }
@@ -23,18 +23,24 @@ module Gitlab
raw_data.assignee.present?
end
- def assignee_id
+ def author
+ @author ||= UserFormatter.new(client, raw_data.user)
+ end
+
+ def author_id
+ @author_id ||= author.gitlab_id || project.creator_id
+ end
+
+ def assignee
if assigned?
- gitlab_user_id(raw_data.assignee.id)
+ @assignee ||= UserFormatter.new(client, raw_data.assignee)
end
end
- def author
- raw_data.user.login
- end
+ def assignee_id
+ return @assignee_id if defined?(@assignee_id)
- def author_id
- gitlab_author_id || project.creator_id
+ @assignee_id = assignee.try(:gitlab_id)
end
def body
@@ -42,10 +48,10 @@ module Gitlab
end
def description
- if gitlab_author_id
+ if author.gitlab_id
body
else
- formatter.author_line(author) + body
+ formatter.author_line(author.login) + body
end
end
diff --git a/lib/gitlab/github_import/user_formatter.rb b/lib/gitlab/github_import/user_formatter.rb
new file mode 100644
index 00000000000..04c2964da20
--- /dev/null
+++ b/lib/gitlab/github_import/user_formatter.rb
@@ -0,0 +1,45 @@
+module Gitlab
+ module GithubImport
+ class UserFormatter
+ attr_reader :client, :raw
+
+ delegate :id, :login, to: :raw, allow_nil: true
+
+ def initialize(client, raw)
+ @client = client
+ @raw = raw
+ end
+
+ def gitlab_id
+ return @gitlab_id if defined?(@gitlab_id)
+
+ @gitlab_id = find_by_external_uid || find_by_email
+ end
+
+ private
+
+ def email
+ @email ||= client.user(raw.login).try(:email)
+ end
+
+ def find_by_email
+ return nil unless email
+
+ User.find_by_any_email(email)
+ .try(:id)
+ end
+
+ def find_by_external_uid
+ return nil unless id
+
+ identities = ::Identity.arel_table
+
+ User.select(:id)
+ .joins(:identities).where(identities[:provider].eq(:github)
+ .and(identities[:extern_uid].eq(id)))
+ .first
+ .try(:id)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index b8a5ac907a4..101b1b80c1e 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -2,7 +2,7 @@ module Gitlab
module GonHelper
def add_gon_variables
gon.api_version = API::API.version
- gon.default_avatar_url = URI::join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s
+ gon.default_avatar_url = URI.join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s
gon.max_file_size = current_application_settings.max_attachment_size
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
gon.shortcuts_path = help_page_path('shortcuts')
diff --git a/lib/gitlab/google_code_import/importer.rb b/lib/gitlab/google_code_import/importer.rb
index 1f4edc36928..b02b9737493 100644
--- a/lib/gitlab/google_code_import/importer.rb
+++ b/lib/gitlab/google_code_import/importer.rb
@@ -310,7 +310,7 @@ module Gitlab
if name == project.import_source
"##{id}"
else
- "#{project.namespace.path}/#{name}##{id}"
+ "#{project.namespace.full_path}/#{name}##{id}"
end
text = "~~#{text}~~" if deleted
text
diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb
index d679edec36b..8b327cfc226 100644
--- a/lib/gitlab/import_export.rb
+++ b/lib/gitlab/import_export.rb
@@ -3,7 +3,7 @@ module Gitlab
extend self
# For every version update, the version history in import_export.md has to be kept up to date.
- VERSION = '0.1.6'
+ VERSION = '0.1.6'.freeze
FILENAME_LIMIT = 50
def export_path(relative_path:)
@@ -35,7 +35,7 @@ module Gitlab
end
def export_filename(project:)
- basename = "#{Time.now.strftime('%Y-%m-%d_%H-%M-%3N')}_#{project.namespace.path}_#{project.path}"
+ basename = "#{Time.now.strftime('%Y-%m-%d_%H-%M-%3N')}_#{project.full_path.tr('/', '_')}"
"#{basename[0..FILENAME_LIMIT]}_export.tar.gz"
end
diff --git a/lib/gitlab/import_export/command_line_util.rb b/lib/gitlab/import_export/command_line_util.rb
index f00c7460e82..90942774a2e 100644
--- a/lib/gitlab/import_export/command_line_util.rb
+++ b/lib/gitlab/import_export/command_line_util.rb
@@ -15,14 +15,6 @@ module Gitlab
execute(%W(#{git_bin_path} --git-dir=#{repo_path} bundle create #{bundle_path} --all))
end
- def git_unbundle(repo_path:, bundle_path:)
- execute(%W(#{git_bin_path} clone --bare #{bundle_path} #{repo_path}))
- end
-
- def git_restore_hooks
- execute(%W(#{Gitlab.config.gitlab_shell.path}/bin/create-hooks) + repository_storage_paths_args)
- end
-
def mkdir_p(path)
FileUtils.mkdir_p(path, mode: DEFAULT_MODE)
FileUtils.chmod(DEFAULT_MODE, path)
@@ -56,10 +48,6 @@ module Gitlab
FileUtils.copy_entry(source, destination)
true
end
-
- def repository_storage_paths_args
- Gitlab.config.repositories.storages.values
- end
end
end
end
diff --git a/lib/gitlab/import_export/importer.rb b/lib/gitlab/import_export/importer.rb
index e9ee47fc090..063ce74ecad 100644
--- a/lib/gitlab/import_export/importer.rb
+++ b/lib/gitlab/import_export/importer.rb
@@ -56,7 +56,7 @@ module Gitlab
end
def path_with_namespace
- File.join(@project.namespace.path, @project.path)
+ File.join(@project.namespace.full_path, @project.path)
end
def repo_path
diff --git a/lib/gitlab/import_export/repo_restorer.rb b/lib/gitlab/import_export/repo_restorer.rb
index 48a9a6fa5e2..c824d3ea9fc 100644
--- a/lib/gitlab/import_export/repo_restorer.rb
+++ b/lib/gitlab/import_export/repo_restorer.rb
@@ -2,6 +2,7 @@ module Gitlab
module ImportExport
class RepoRestorer
include Gitlab::ImportExport::CommandLineUtil
+ include Gitlab::ShellAdapter
def initialize(project:, shared:, path_to_bundle:)
@project = project
@@ -12,29 +13,11 @@ module Gitlab
def restore
return true unless File.exist?(@path_to_bundle)
- mkdir_p(path_to_repo)
-
- git_unbundle(repo_path: path_to_repo, bundle_path: @path_to_bundle) && repo_restore_hooks
+ gitlab_shell.import_repository(@project.repository_storage_path, @project.path_with_namespace, @path_to_bundle)
rescue => e
@shared.error(e)
false
end
-
- private
-
- def path_to_repo
- @project.repository.path_to_repo
- end
-
- def repo_restore_hooks
- return true if wiki?
-
- git_restore_hooks
- end
-
- def wiki?
- @project.class.name == 'ProjectWiki'
- end
end
end
end
diff --git a/lib/gitlab/ldap/person.rb b/lib/gitlab/ldap/person.rb
index 7084fd1767d..43eb73250b7 100644
--- a/lib/gitlab/ldap/person.rb
+++ b/lib/gitlab/ldap/person.rb
@@ -43,9 +43,7 @@ module Gitlab
attribute_value(:email)
end
- def dn
- entry.dn
- end
+ delegate :dn, to: :entry
private
diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb
index 3d1ba33ec68..857e0abf710 100644
--- a/lib/gitlab/metrics.rb
+++ b/lib/gitlab/metrics.rb
@@ -112,7 +112,7 @@ module Gitlab
def self.tag_transaction(name, value)
trans = current_transaction
- trans.add_tag(name, value) if trans
+ trans&.add_tag(name, value)
end
# Sets the action of the current transaction (if any)
@@ -121,7 +121,7 @@ module Gitlab
def self.action=(action)
trans = current_transaction
- trans.action = action if trans
+ trans&.action = action
end
# Tracks an event.
@@ -130,7 +130,7 @@ module Gitlab
def self.add_event(*args)
trans = current_transaction
- trans.add_event(*args) if trans
+ trans&.add_event(*args)
end
# Returns the prefix to use for the name of a series.
diff --git a/lib/gitlab/metrics/instrumentation.rb b/lib/gitlab/metrics/instrumentation.rb
index 4b7a791e497..6aa38542cb4 100644
--- a/lib/gitlab/metrics/instrumentation.rb
+++ b/lib/gitlab/metrics/instrumentation.rb
@@ -143,11 +143,12 @@ module Gitlab
# signature this would break things. As a result we'll make sure the
# generated method _only_ accepts regular arguments if the underlying
# method also accepts them.
- if method.arity == 0
- args_signature = ''
- else
- args_signature = '*args'
- end
+ args_signature =
+ if method.arity == 0
+ ''
+ else
+ '*args'
+ end
proxy_module.class_eval <<-EOF, __FILE__, __LINE__ + 1
def #{name}(#{args_signature})
diff --git a/lib/gitlab/metrics/rack_middleware.rb b/lib/gitlab/metrics/rack_middleware.rb
index 47f88727fc8..adc0db1a874 100644
--- a/lib/gitlab/metrics/rack_middleware.rb
+++ b/lib/gitlab/metrics/rack_middleware.rb
@@ -2,8 +2,8 @@ module Gitlab
module Metrics
# Rack middleware for tracking Rails and Grape requests.
class RackMiddleware
- CONTROLLER_KEY = 'action_controller.instance'
- ENDPOINT_KEY = 'api.endpoint'
+ CONTROLLER_KEY = 'action_controller.instance'.freeze
+ ENDPOINT_KEY = 'api.endpoint'.freeze
CONTENT_TYPES = {
'text/html' => :html,
'text/plain' => :txt,
@@ -14,7 +14,7 @@ module Gitlab
'image/jpeg' => :jpeg,
'image/gif' => :gif,
'image/svg+xml' => :svg
- }
+ }.freeze
def initialize(app)
@app = app
diff --git a/lib/gitlab/metrics/subscribers/action_view.rb b/lib/gitlab/metrics/subscribers/action_view.rb
index 2e9dd4645e3..d435a33e9c7 100644
--- a/lib/gitlab/metrics/subscribers/action_view.rb
+++ b/lib/gitlab/metrics/subscribers/action_view.rb
@@ -5,7 +5,7 @@ module Gitlab
class ActionView < ActiveSupport::Subscriber
attach_to :action_view
- SERIES = 'views'
+ SERIES = 'views'.freeze
def render_template(event)
track(event) if current_transaction
diff --git a/lib/gitlab/metrics/system.rb b/lib/gitlab/metrics/system.rb
index 287b7a83547..3aaebb3e9c3 100644
--- a/lib/gitlab/metrics/system.rb
+++ b/lib/gitlab/metrics/system.rb
@@ -11,7 +11,7 @@ module Gitlab
mem = 0
match = File.read('/proc/self/status').match(/VmRSS:\s+(\d+)/)
- if match and match[1]
+ if match && match[1]
mem = match[1].to_f * 1024
end
diff --git a/lib/gitlab/metrics/transaction.rb b/lib/gitlab/metrics/transaction.rb
index 7bc16181be6..4f9fb1c7853 100644
--- a/lib/gitlab/metrics/transaction.rb
+++ b/lib/gitlab/metrics/transaction.rb
@@ -5,7 +5,7 @@ module Gitlab
THREAD_KEY = :_gitlab_metrics_transaction
# The series to store events (e.g. Git pushes) in.
- EVENT_SERIES = 'events'
+ EVENT_SERIES = 'events'.freeze
attr_reader :tags, :values, :method, :metrics
diff --git a/lib/gitlab/middleware/multipart.rb b/lib/gitlab/middleware/multipart.rb
index dd99f9bb7d7..fee741b47be 100644
--- a/lib/gitlab/middleware/multipart.rb
+++ b/lib/gitlab/middleware/multipart.rb
@@ -26,7 +26,7 @@
module Gitlab
module Middleware
class Multipart
- RACK_ENV_KEY = 'HTTP_GITLAB_WORKHORSE_MULTIPART_FIELDS'
+ RACK_ENV_KEY = 'HTTP_GITLAB_WORKHORSE_MULTIPART_FIELDS'.freeze
class Handler
def initialize(env, message)
diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb
index 96ed20af918..95d2f559588 100644
--- a/lib/gitlab/o_auth/user.rb
+++ b/lib/gitlab/o_auth/user.rb
@@ -29,12 +29,11 @@ module Gitlab
def save(provider = 'OAuth')
unauthorized_to_create unless gl_user
- if needs_blocking?
- gl_user.save!
- gl_user.block
- else
- gl_user.save!
- end
+ block_after_save = needs_blocking?
+
+ gl_user.save!
+
+ gl_user.block if block_after_save
log.info "(#{provider}) saving user #{auth_hash.email} from login with extern_uid => #{auth_hash.uid}"
gl_user
diff --git a/lib/gitlab/optimistic_locking.rb b/lib/gitlab/optimistic_locking.rb
index 879d46446b3..962ff4d3985 100644
--- a/lib/gitlab/optimistic_locking.rb
+++ b/lib/gitlab/optimistic_locking.rb
@@ -1,12 +1,12 @@
module Gitlab
module OptimisticLocking
- extend self
+ module_function
def retry_lock(subject, retries = 100, &block)
loop do
begin
ActiveRecord::Base.transaction do
- return block.call(subject)
+ return yield(subject)
end
rescue ActiveRecord::StaleObjectError
retries -= 1
@@ -15,5 +15,7 @@ module Gitlab
end
end
end
+
+ alias_method :retry_optimistic_lock, :retry_lock
end
end
diff --git a/lib/gitlab/other_markup.rb b/lib/gitlab/other_markup.rb
index 4e2f8ed5587..e67acf28c94 100644
--- a/lib/gitlab/other_markup.rb
+++ b/lib/gitlab/other_markup.rb
@@ -17,6 +17,9 @@ module Gitlab
html = Banzai.post_process(html, context)
+ filter = Banzai::Filter::SanitizationFilter.new(html)
+ html = filter.call.to_s
+
html.html_safe
end
end
diff --git a/lib/gitlab/redis.rb b/lib/gitlab/redis.rb
index 9384102acec..62dbd429156 100644
--- a/lib/gitlab/redis.rb
+++ b/lib/gitlab/redis.rb
@@ -4,24 +4,15 @@ require 'active_support/core_ext/hash/keys'
module Gitlab
class Redis
- CACHE_NAMESPACE = 'cache:gitlab'
- SESSION_NAMESPACE = 'session:gitlab'
- SIDEKIQ_NAMESPACE = 'resque:gitlab'
- MAILROOM_NAMESPACE = 'mail_room:gitlab'
- DEFAULT_REDIS_URL = 'redis://localhost:6379'
+ CACHE_NAMESPACE = 'cache:gitlab'.freeze
+ SESSION_NAMESPACE = 'session:gitlab'.freeze
+ SIDEKIQ_NAMESPACE = 'resque:gitlab'.freeze
+ MAILROOM_NAMESPACE = 'mail_room:gitlab'.freeze
+ DEFAULT_REDIS_URL = 'redis://localhost:6379'.freeze
CONFIG_FILE = File.expand_path('../../config/resque.yml', __dir__)
class << self
- # Do NOT cache in an instance variable. Result may be mutated by caller.
- def params
- new.params
- end
-
- # Do NOT cache in an instance variable. Result may be mutated by caller.
- # @deprecated Use .params instead to get sentinel support
- def url
- new.url
- end
+ delegate :params, :url, to: :new
def with
@pool ||= ConnectionPool.new(size: pool_size) { ::Redis.new(params) }
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
index 11c0b01f0dc..7668ecacc4b 100644
--- a/lib/gitlab/reference_extractor.rb
+++ b/lib/gitlab/reference_extractor.rb
@@ -1,13 +1,12 @@
module Gitlab
# Extract possible GFM references from an arbitrary String for further processing.
class ReferenceExtractor < Banzai::ReferenceExtractor
- REFERABLES = %i(user issue label milestone merge_request snippet commit commit_range)
+ REFERABLES = %i(user issue label milestone merge_request snippet commit commit_range directly_addressed_user).freeze
attr_accessor :project, :current_user, :author
def initialize(project, current_user = nil)
@project = project
@current_user = current_user
-
@references = {}
super()
@@ -21,6 +20,11 @@ module Gitlab
super(type, project, current_user)
end
+ def reset_memoized_values
+ @references = {}
+ super()
+ end
+
REFERABLES.each do |type|
define_method("#{type}s") do
@references[type] ||= references(type)
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index a3fa7c1331a..5e5f5ff1589 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -5,13 +5,18 @@ module Gitlab
# The namespace regex is used in Javascript to validate usernames in the "Register" form. However, Javascript
# does not support the negative lookbehind assertion (?<!) that disallows usernames ending in `.git` and `.atom`.
# Since this is a non-trivial problem to solve in Javascript (heavily complicate the regex, modify view code to
- # allow non-regex validatiions, etc), `NAMESPACE_REGEX_STR_SIMPLE` serves as a Javascript-compatible version of
+ # allow non-regex validatiions, etc), `NAMESPACE_REGEX_STR_JS` serves as a Javascript-compatible version of
# `NAMESPACE_REGEX_STR`, with the negative lookbehind assertion removed. This means that the client-side validation
# will pass for usernames ending in `.atom` and `.git`, but will be caught by the server-side validation.
PATH_REGEX_STR = '[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*'.freeze
- NAMESPACE_REGEX_STR_SIMPLE = PATH_REGEX_STR + '[a-zA-Z0-9_\-]|[a-zA-Z0-9_]'.freeze
- NAMESPACE_REGEX_STR = '(?:' + NAMESPACE_REGEX_STR_SIMPLE + ')(?<!\.git|\.atom)'.freeze
- PROJECT_REGEX_STR = PATH_REGEX_STR + '(?<!\.git|\.atom)'.freeze
+ NAMESPACE_REGEX_STR_JS = PATH_REGEX_STR + '[a-zA-Z0-9_\-]|[a-zA-Z0-9_]'.freeze
+ NO_SUFFIX_REGEX_STR = '(?<!\.git|\.atom)'.freeze
+ NAMESPACE_REGEX_STR = "(?:#{NAMESPACE_REGEX_STR_JS})#{NO_SUFFIX_REGEX_STR}".freeze
+ PROJECT_REGEX_STR = "(?:#{PATH_REGEX_STR})#{NO_SUFFIX_REGEX_STR}".freeze
+
+ # Same as NAMESPACE_REGEX_STR but allows `/` in the path.
+ # So `group/subgroup` will match this regex but not NAMESPACE_REGEX_STR
+ FULL_NAMESPACE_REGEX_STR = "(?:#{NAMESPACE_REGEX_STR}/)*#{NAMESPACE_REGEX_STR}".freeze
def namespace_regex
@namespace_regex ||= /\A#{NAMESPACE_REGEX_STR}\z/.freeze
diff --git a/lib/gitlab/request_profiler.rb b/lib/gitlab/request_profiler.rb
index 8130e55351e..0c9ab759e81 100644
--- a/lib/gitlab/request_profiler.rb
+++ b/lib/gitlab/request_profiler.rb
@@ -2,7 +2,7 @@ require 'fileutils'
module Gitlab
module RequestProfiler
- PROFILES_DIR = "#{Gitlab.config.shared.path}/tmp/requests_profiles"
+ PROFILES_DIR = "#{Gitlab.config.shared.path}/tmp/requests_profiles".freeze
def profile_token
Rails.cache.fetch('profile-token') do
diff --git a/lib/gitlab/saml/user.rb b/lib/gitlab/saml/user.rb
index f253dc7477e..8a7cc690046 100644
--- a/lib/gitlab/saml/user.rb
+++ b/lib/gitlab/saml/user.rb
@@ -28,11 +28,12 @@ module Gitlab
if external_users_enabled? && @user
# Check if there is overlap between the user's groups and the external groups
# setting then set user as external or internal.
- if (auth_hash.groups & Gitlab::Saml::Config.external_groups).empty?
- @user.external = false
- else
- @user.external = true
- end
+ @user.external =
+ if (auth_hash.groups & Gitlab::Saml::Config.external_groups).empty?
+ false
+ else
+ true
+ end
end
@user
diff --git a/lib/gitlab/sanitizers/svg/whitelist.rb b/lib/gitlab/sanitizers/svg/whitelist.rb
index 7b6b70d8dbc..d50f826f924 100644
--- a/lib/gitlab/sanitizers/svg/whitelist.rb
+++ b/lib/gitlab/sanitizers/svg/whitelist.rb
@@ -6,18 +6,19 @@ module Gitlab
module SVG
class Whitelist
ALLOWED_ELEMENTS = %w[
- a altGlyph altGlyphDef altGlyphItem animate
- animateColor animateMotion animateTransform circle clipPath color-profile
- cursor defs desc ellipse feBlend feColorMatrix feComponentTransfer
- feComposite feConvolveMatrix feDiffuseLighting feDisplacementMap
- feDistantLight feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur
- feImage feMerge feMergeNode feMorphology feOffset fePointLight
- feSpecularLighting feSpotLight feTile feTurbulence filter font font-face
- font-face-format font-face-name font-face-src font-face-uri foreignObject
- g glyph glyphRef hkern image line linearGradient marker mask metadata
- missing-glyph mpath path pattern polygon polyline radialGradient rect
- script set stop style svg switch symbol text textPath title tref tspan use
- view vkern].freeze
+ a altGlyph altGlyphDef altGlyphItem animate
+ animateColor animateMotion animateTransform circle clipPath color-profile
+ cursor defs desc ellipse feBlend feColorMatrix feComponentTransfer
+ feComposite feConvolveMatrix feDiffuseLighting feDisplacementMap
+ feDistantLight feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur
+ feImage feMerge feMergeNode feMorphology feOffset fePointLight
+ feSpecularLighting feSpotLight feTile feTurbulence filter font font-face
+ font-face-format font-face-name font-face-src font-face-uri foreignObject
+ g glyph glyphRef hkern image line linearGradient marker mask metadata
+ missing-glyph mpath path pattern polygon polyline radialGradient rect
+ script set stop style svg switch symbol text textPath title tref tspan use
+ view vkern
+ ].freeze
ALLOWED_DATA_ATTRIBUTES_IN_ELEMENTS = %w[svg].freeze
diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb
index c9c65f76f4b..ccfa517e04b 100644
--- a/lib/gitlab/search_results.rb
+++ b/lib/gitlab/search_results.rb
@@ -56,11 +56,12 @@ module Gitlab
def issues
issues = IssuesFinder.new(current_user).execute.where(project_id: project_ids_relation)
- if query =~ /#(\d+)\z/
- issues = issues.where(iid: $1)
- else
- issues = issues.full_search(query)
- end
+ issues =
+ if query =~ /#(\d+)\z/
+ issues.where(iid: $1)
+ else
+ issues.full_search(query)
+ end
issues.order('updated_at DESC')
end
@@ -73,11 +74,12 @@ module Gitlab
def merge_requests
merge_requests = MergeRequestsFinder.new(current_user).execute.in_projects(project_ids_relation)
- if query =~ /[#!](\d+)\z/
- merge_requests = merge_requests.where(iid: $1)
- else
- merge_requests = merge_requests.full_search(query)
- end
+ merge_requests =
+ if query =~ /[#!](\d+)\z/
+ merge_requests.where(iid: $1)
+ else
+ merge_requests.full_search(query)
+ end
merge_requests.order('updated_at DESC')
end
diff --git a/lib/gitlab/seeder.rb b/lib/gitlab/seeder.rb
index 7cf506ebe64..b7f825e8284 100644
--- a/lib/gitlab/seeder.rb
+++ b/lib/gitlab/seeder.rb
@@ -18,7 +18,7 @@ def Notify.deliver_later
self
end
eos
- eval(code)
+ eval(code) # rubocop:disable Security/Eval
end
end
end
diff --git a/lib/gitlab/serialize/ci/variables.rb b/lib/gitlab/serializer/ci/variables.rb
index 3a9443bfcd9..c059c454eac 100644
--- a/lib/gitlab/serialize/ci/variables.rb
+++ b/lib/gitlab/serializer/ci/variables.rb
@@ -1,5 +1,5 @@
module Gitlab
- module Serialize
+ module Serializer
module Ci
# This serializer could make sure our YAML variables' keys and values
# are always strings. This is more for legacy build data because
diff --git a/lib/gitlab/serializer/pagination.rb b/lib/gitlab/serializer/pagination.rb
new file mode 100644
index 00000000000..bf2c0acc729
--- /dev/null
+++ b/lib/gitlab/serializer/pagination.rb
@@ -0,0 +1,36 @@
+module Gitlab
+ module Serializer
+ class Pagination
+ class InvalidResourceError < StandardError; end
+ include ::API::Helpers::Pagination
+
+ def initialize(request, response)
+ @request = request
+ @response = response
+ end
+
+ def paginate(resource)
+ if resource.respond_to?(:page)
+ super(resource)
+ else
+ raise InvalidResourceError
+ end
+ end
+
+ private
+
+ # Methods needed by `API::Helpers::Pagination`
+ #
+
+ attr_reader :request
+
+ def params
+ @request.query_parameters
+ end
+
+ def header(header, value)
+ @response.headers[header] = value
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index 82e194c1af1..7374d2bc8b8 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -80,8 +80,10 @@ module Gitlab
# import_repository("/path/to/storage", "gitlab/gitlab-ci", "https://github.com/randx/six.git")
#
def import_repository(storage, name, url)
- output, status = Popen::popen([gitlab_shell_projects_path, 'import-project',
- storage, "#{name}.git", url, '900'])
+ # Timeout should be less than 900 ideally, to prevent the memory killer
+ # to silently kill the process without knowing we are timing out here.
+ output, status = Popen.popen([gitlab_shell_projects_path, 'import-project',
+ storage, "#{name}.git", url, '800'])
raise Error, output unless status.zero?
true
end
@@ -143,7 +145,7 @@ module Gitlab
# batch_add_keys { |adder| adder.add_key("key-42", "sha-rsa ...") }
def batch_add_keys(&block)
IO.popen(%W(#{gitlab_shell_path}/bin/gitlab-keys batch-add-keys), 'w') do |io|
- block.call(KeyAdder.new(io))
+ yield(KeyAdder.new(io))
end
end
@@ -172,7 +174,7 @@ module Gitlab
# add_namespace("/path/to/storage", "gitlab")
#
def add_namespace(storage, name)
- FileUtils.mkdir(full_path(storage, name), mode: 0770) unless exists?(storage, name)
+ FileUtils.mkdir_p(full_path(storage, name), mode: 0770) unless exists?(storage, name)
end
# Remove directory from repositories storage
diff --git a/lib/gitlab/sherlock/query.rb b/lib/gitlab/sherlock/query.rb
index 4917c4ae2ac..99e56e923eb 100644
--- a/lib/gitlab/sherlock/query.rb
+++ b/lib/gitlab/sherlock/query.rb
@@ -94,11 +94,12 @@ module Gitlab
private
def raw_explain(query)
- if Gitlab::Database.postgresql?
- explain = "EXPLAIN ANALYZE #{query};"
- else
- explain = "EXPLAIN #{query};"
- end
+ explain =
+ if Gitlab::Database.postgresql?
+ "EXPLAIN ANALYZE #{query};"
+ else
+ "EXPLAIN #{query};"
+ end
ActiveRecord::Base.connection.execute(explain)
end
diff --git a/lib/gitlab/sidekiq_status/client_middleware.rb b/lib/gitlab/sidekiq_status/client_middleware.rb
index 779a9998b22..d47609f490d 100644
--- a/lib/gitlab/sidekiq_status/client_middleware.rb
+++ b/lib/gitlab/sidekiq_status/client_middleware.rb
@@ -2,7 +2,7 @@ module Gitlab
module SidekiqStatus
class ClientMiddleware
def call(_, job, _, _)
- SidekiqStatus.set(job['jid'])
+ Gitlab::SidekiqStatus.set(job['jid'])
yield
end
end
diff --git a/lib/gitlab/sidekiq_status/server_middleware.rb b/lib/gitlab/sidekiq_status/server_middleware.rb
index 31dfa46ff9d..ceab10b8301 100644
--- a/lib/gitlab/sidekiq_status/server_middleware.rb
+++ b/lib/gitlab/sidekiq_status/server_middleware.rb
@@ -4,7 +4,7 @@ module Gitlab
def call(worker, job, queue)
ret = yield
- SidekiqStatus.unset(job['jid'])
+ Gitlab::SidekiqStatus.unset(job['jid'])
ret
end
diff --git a/lib/gitlab/slash_commands/extractor.rb b/lib/gitlab/slash_commands/extractor.rb
index a672e5e4855..6dbb467d70d 100644
--- a/lib/gitlab/slash_commands/extractor.rb
+++ b/lib/gitlab/slash_commands/extractor.rb
@@ -103,7 +103,7 @@ module Gitlab
(?<cmd>#{Regexp.union(names)})
(?:
[ ]
- (?<arg>[^\/\n]*)
+ (?<arg>[^\n]*)
)?
(?:\n|$)
)
diff --git a/lib/gitlab/snippet_search_results.rb b/lib/gitlab/snippet_search_results.rb
index 9e01f02029c..b85f70e450e 100644
--- a/lib/gitlab/snippet_search_results.rb
+++ b/lib/gitlab/snippet_search_results.rb
@@ -31,11 +31,11 @@ module Gitlab
private
def snippet_titles
- limit_snippets.search(query).order('updated_at DESC')
+ limit_snippets.search(query).order('updated_at DESC').includes(:author)
end
def snippet_blobs
- limit_snippets.search_code(query).order('updated_at DESC')
+ limit_snippets.search_code(query).order('updated_at DESC').includes(:author)
end
def default_scope
diff --git a/lib/gitlab/template/gitlab_ci_yml_template.rb b/lib/gitlab/template/gitlab_ci_yml_template.rb
index 9d2ecee9756..fd040148a1e 100644
--- a/lib/gitlab/template/gitlab_ci_yml_template.rb
+++ b/lib/gitlab/template/gitlab_ci_yml_template.rb
@@ -28,7 +28,7 @@ module Gitlab
end
def dropdown_names(context)
- categories = context == 'autodeploy' ? ['Auto deploy'] : ['General', 'Pages']
+ categories = context == 'autodeploy' ? ['Auto deploy'] : %w(General Pages)
super().slice(*categories)
end
end
diff --git a/lib/gitlab/themes.rb b/lib/gitlab/themes.rb
deleted file mode 100644
index 19ab76ae80f..00000000000
--- a/lib/gitlab/themes.rb
+++ /dev/null
@@ -1,87 +0,0 @@
-module Gitlab
- # Module containing GitLab's application theme definitions and helper methods
- # for accessing them.
- module Themes
- extend self
-
- # Theme ID used when no `default_theme` configuration setting is provided.
- APPLICATION_DEFAULT = 2
-
- # Struct class representing a single Theme
- Theme = Struct.new(:id, :name, :css_class)
-
- # All available Themes
- THEMES = [
- Theme.new(1, 'Graphite', 'ui_graphite'),
- Theme.new(2, 'Charcoal', 'ui_charcoal'),
- Theme.new(3, 'Green', 'ui_green'),
- Theme.new(4, 'Black', 'ui_black'),
- Theme.new(5, 'Violet', 'ui_violet'),
- Theme.new(6, 'Blue', 'ui_blue')
- ].freeze
-
- # Convenience method to get a space-separated String of all the theme
- # classes that might be applied to the `body` element
- #
- # Returns a String
- def body_classes
- THEMES.collect(&:css_class).uniq.join(' ')
- end
-
- # Get a Theme by its ID
- #
- # If the ID is invalid, returns the default Theme.
- #
- # id - Integer ID
- #
- # Returns a Theme
- def by_id(id)
- THEMES.detect { |t| t.id == id } || default
- end
-
- # Returns the number of defined Themes
- def count
- THEMES.size
- end
-
- # Get the default Theme
- #
- # Returns a Theme
- def default
- by_id(default_id)
- end
-
- # Iterate through each Theme
- #
- # Yields the Theme object
- def each(&block)
- THEMES.each(&block)
- end
-
- # Get the Theme for the specified user, or the default
- #
- # user - User record
- #
- # Returns a Theme
- def for_user(user)
- if user
- by_id(user.theme_id)
- else
- default
- end
- end
-
- private
-
- def default_id
- id = Gitlab.config.gitlab.default_theme.to_i
-
- # Prevent an invalid configuration setting from causing an infinite loop
- if id < THEMES.first.id || id > THEMES.last.id
- APPLICATION_DEFAULT
- else
- id
- end
- end
- end
-end
diff --git a/lib/gitlab/upgrader.rb b/lib/gitlab/upgrader.rb
index e78d0c34a02..961df0468a4 100644
--- a/lib/gitlab/upgrader.rb
+++ b/lib/gitlab/upgrader.rb
@@ -46,7 +46,7 @@ module Gitlab
git_tags = fetch_git_tags
git_tags = git_tags.select { |version| version =~ /v\d+\.\d+\.\d+\Z/ }
git_versions = git_tags.map { |tag| Gitlab::VersionInfo.parse(tag.match(/v\d+\.\d+\.\d+/).to_s) }
- "v#{git_versions.sort.last.to_s}"
+ "v#{git_versions.sort.last}"
end
def fetch_git_tags
@@ -59,15 +59,18 @@ module Gitlab
"Stash changed files" => %W(#{Gitlab.config.git.bin_path} stash),
"Get latest code" => %W(#{Gitlab.config.git.bin_path} fetch),
"Switch to new version" => %W(#{Gitlab.config.git.bin_path} checkout v#{latest_version}),
- "Install gems" => %W(bundle),
- "Migrate DB" => %W(bundle exec rake db:migrate),
- "Recompile assets" => %W(bundle exec rake gitlab:assets:clean gitlab:assets:compile),
- "Clear cache" => %W(bundle exec rake cache:clear)
+ "Install gems" => %w(bundle),
+ "Migrate DB" => %w(bundle exec rake db:migrate),
+ "Recompile assets" => %w(bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile),
+ "Clear cache" => %w(bundle exec rake cache:clear)
}
end
def env
- { 'RAILS_ENV' => 'production' }
+ {
+ 'RAILS_ENV' => 'production',
+ 'NODE_ENV' => 'production'
+ }
end
def upgrade
diff --git a/lib/gitlab/url_sanitizer.rb b/lib/gitlab/url_sanitizer.rb
index 19dad699edf..1f0d96088cf 100644
--- a/lib/gitlab/url_sanitizer.rb
+++ b/lib/gitlab/url_sanitizer.rb
@@ -1,7 +1,7 @@
module Gitlab
class UrlSanitizer
def self.sanitize(content)
- regexp = URI::Parser.new.make_regexp(['http', 'https', 'ssh', 'git'])
+ regexp = URI::Parser.new.make_regexp(%w(http https ssh git))
content.gsub(regexp) { |url| new(url).masked_url }
rescue Addressable::URI::InvalidURIError
diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb
index a4e966e4016..b28708c34e1 100644
--- a/lib/gitlab/visibility_level.rb
+++ b/lib/gitlab/visibility_level.rb
@@ -33,9 +33,7 @@ module Gitlab
PUBLIC = 20 unless const_defined?(:PUBLIC)
class << self
- def values
- options.values
- end
+ delegate :values, to: :options
def options
{
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index c8872df8a93..3ff9f9eb5e7 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -4,10 +4,10 @@ require 'securerandom'
module Gitlab
class Workhorse
- SEND_DATA_HEADER = 'Gitlab-Workhorse-Send-Data'
- VERSION_FILE = 'GITLAB_WORKHORSE_VERSION'
- INTERNAL_API_CONTENT_TYPE = 'application/vnd.gitlab-workhorse+json'
- INTERNAL_API_REQUEST_HEADER = 'Gitlab-Workhorse-Api-Request'
+ SEND_DATA_HEADER = 'Gitlab-Workhorse-Send-Data'.freeze
+ VERSION_FILE = 'GITLAB_WORKHORSE_VERSION'.freeze
+ INTERNAL_API_CONTENT_TYPE = 'application/vnd.gitlab-workhorse+json'.freeze
+ INTERNAL_API_REQUEST_HEADER = 'Gitlab-Workhorse-Api-Request'.freeze
# Supposedly the effective key size for HMAC-SHA256 is 256 bits, i.e. 32
# bytes https://tools.ietf.org/html/rfc4868#section-2.6
diff --git a/lib/tasks/brakeman.rake b/lib/tasks/brakeman.rake
index d5a402907d8..2301ec9b228 100644
--- a/lib/tasks/brakeman.rake
+++ b/lib/tasks/brakeman.rake
@@ -2,7 +2,7 @@ desc 'Security check via brakeman'
task :brakeman do
# We get 0 warnings at level 'w3' but we would like to reach 'w2'. Merge
# requests are welcome!
- if system(*%W(brakeman --no-progress --skip-files lib/backup/repository.rb -w3 -z))
+ if system(*%w(brakeman --no-progress --skip-files lib/backup/repository.rb -w3 -z))
puts 'Security check succeed'
else
puts 'Security check failed'
diff --git a/lib/tasks/cache.rake b/lib/tasks/cache.rake
index 78ae187817a..d55923673b1 100644
--- a/lib/tasks/cache.rake
+++ b/lib/tasks/cache.rake
@@ -1,7 +1,7 @@
namespace :cache do
namespace :clear do
REDIS_CLEAR_BATCH_SIZE = 1000 # There seems to be no speedup when pushing beyond 1,000
- REDIS_SCAN_START_STOP = '0' # Magic value, see http://redis.io/commands/scan
+ REDIS_SCAN_START_STOP = '0'.freeze # Magic value, see http://redis.io/commands/scan
desc "GitLab | Clear redis cache"
task redis: :environment do
diff --git a/lib/tasks/dev.rake b/lib/tasks/dev.rake
index 5e94fba97bf..e65609d7001 100644
--- a/lib/tasks/dev.rake
+++ b/lib/tasks/dev.rake
@@ -2,7 +2,7 @@ task dev: ["dev:setup"]
namespace :dev do
desc "GitLab | Setup developer environment (db, fixtures)"
- task :setup => :environment do
+ task setup: :environment do
ENV['force'] = 'yes'
Rake::Task["gitlab:setup"].invoke
Rake::Task["gitlab:shell:setup"].invoke
diff --git a/lib/tasks/downtime_check.rake b/lib/tasks/downtime_check.rake
index afe5d42910c..557f4fef10b 100644
--- a/lib/tasks/downtime_check.rake
+++ b/lib/tasks/downtime_check.rake
@@ -1,10 +1,10 @@
desc 'Checks if migrations in a branch require downtime'
task downtime_check: :environment do
- if defined?(Gitlab::License)
- repo = 'gitlab-ee'
- else
- repo = 'gitlab-ce'
- end
+ repo = if defined?(Gitlab::License)
+ 'gitlab-ee'
+ else
+ 'gitlab-ce'
+ end
`git fetch https://gitlab.com/gitlab-org/#{repo}.git --depth 1`
diff --git a/lib/tasks/eslint.rake b/lib/tasks/eslint.rake
index d43cbad1909..51f5d768102 100644
--- a/lib/tasks/eslint.rake
+++ b/lib/tasks/eslint.rake
@@ -1,7 +1,8 @@
unless Rails.env.production?
desc "GitLab | Run ESLint"
- task :eslint do
- system("npm", "run", "eslint")
+ task eslint: ['yarn:check'] do
+ unless system('yarn run eslint')
+ abort('rake eslint failed')
+ end
end
end
-
diff --git a/lib/tasks/flay.rake b/lib/tasks/flay.rake
index e9587595fef..7ad2b2e4d39 100644
--- a/lib/tasks/flay.rake
+++ b/lib/tasks/flay.rake
@@ -1,6 +1,6 @@
desc 'Code duplication analyze via flay'
task :flay do
- output = %x(bundle exec flay --mass 35 app/ lib/gitlab/)
+ output = `bundle exec flay --mass 35 app/ lib/gitlab/`
if output.include? "Similar code found"
puts output
diff --git a/lib/tasks/gitlab/assets.rake b/lib/tasks/gitlab/assets.rake
index b6ef8260191..098f9851b45 100644
--- a/lib/tasks/gitlab/assets.rake
+++ b/lib/tasks/gitlab/assets.rake
@@ -1,26 +1,26 @@
namespace :gitlab do
namespace :assets do
desc 'GitLab | Assets | Compile all frontend assets'
- task :compile do
- Rake::Task['assets:precompile'].invoke
- Rake::Task['webpack:compile'].invoke
- Rake::Task['gitlab:assets:fix_urls'].invoke
- end
+ task compile: [
+ 'yarn:check',
+ 'assets:precompile',
+ 'webpack:compile',
+ 'gitlab:assets:fix_urls'
+ ]
desc 'GitLab | Assets | Clean up old compiled frontend assets'
- task :clean do
- Rake::Task['assets:clean'].invoke
- end
+ task clean: ['assets:clean']
desc 'GitLab | Assets | Remove all compiled frontend assets'
- task :purge do
- Rake::Task['assets:clobber'].invoke
- end
+ task purge: ['assets:clobber']
+
+ desc 'GitLab | Assets | Uninstall frontend dependencies'
+ task purge_modules: ['yarn:clobber']
desc 'GitLab | Assets | Fix all absolute url references in CSS'
task :fix_urls do
css_files = Dir['public/assets/*.css']
- css_files.each do | file |
+ css_files.each do |file|
# replace url(/assets/*) with url(./*)
puts "Fixing #{file}"
system "sed", "-i", "-e", 's/url(\([\"\']\?\)\/assets\//url(\1.\//g', file
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index 35c4194e87c..38edd49b6ed 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -6,8 +6,6 @@ namespace :gitlab do
gitlab:ldap:check
gitlab:app:check}
-
-
namespace :app do
desc "GitLab | Check the configuration of the GitLab Rails app"
task check: :environment do
@@ -34,7 +32,6 @@ namespace :gitlab do
finished_checking "GitLab"
end
-
# Checks
########################
@@ -194,7 +191,7 @@ namespace :gitlab do
def check_migrations_are_up
print "All migrations up? ... "
- migration_status, _ = Gitlab::Popen.popen(%W(bundle exec rake db:migrate:status))
+ migration_status, _ = Gitlab::Popen.popen(%w(bundle exec rake db:migrate:status))
unless migration_status =~ /down\s+\d{14}/
puts "yes".color(:green)
@@ -279,7 +276,7 @@ namespace :gitlab do
upload_path_tmp = File.join(upload_path, 'tmp')
if File.stat(upload_path).mode == 040700
- unless Dir.exists?(upload_path_tmp)
+ unless Dir.exist?(upload_path_tmp)
puts 'skipped (no tmp uploads folder yet)'.color(:magenta)
return
end
@@ -316,7 +313,7 @@ namespace :gitlab do
min_redis_version = "2.8.0"
print "Redis version >= #{min_redis_version}? ... "
- redis_version = run_command(%W(redis-cli --version))
+ redis_version = run_command(%w(redis-cli --version))
redis_version = redis_version.try(:match, /redis-cli (\d+\.\d+\.\d+)/)
if redis_version &&
(Gem::Version.new(redis_version[1]) > Gem::Version.new(min_redis_version))
@@ -351,7 +348,6 @@ namespace :gitlab do
finished_checking "GitLab Shell"
end
-
# Checks
########################
@@ -387,7 +383,7 @@ namespace :gitlab do
unless File.exist?(repo_base_path)
puts "can't check because of previous errors".color(:magenta)
- return
+ break
end
unless File.symlink?(repo_base_path)
@@ -410,7 +406,7 @@ namespace :gitlab do
unless File.exist?(repo_base_path)
puts "can't check because of previous errors".color(:magenta)
- return
+ break
end
if File.stat(repo_base_path).mode.to_s(8).ends_with?("2770")
@@ -440,7 +436,7 @@ namespace :gitlab do
unless File.exist?(repo_base_path)
puts "can't check because of previous errors".color(:magenta)
- return
+ break
end
uid = uid_for(gitlab_shell_ssh_user)
@@ -493,7 +489,6 @@ namespace :gitlab do
)
fix_and_rerun
end
-
end
end
@@ -565,8 +560,6 @@ namespace :gitlab do
end
end
-
-
namespace :sidekiq do
desc "GitLab | Check the configuration of Sidekiq"
task check: :environment do
@@ -579,7 +572,6 @@ namespace :gitlab do
finished_checking "Sidekiq"
end
-
# Checks
########################
@@ -621,12 +613,11 @@ namespace :gitlab do
end
def sidekiq_process_count
- ps_ux, _ = Gitlab::Popen.popen(%W(ps ux))
+ ps_ux, _ = Gitlab::Popen.popen(%w(ps ux))
ps_ux.scan(/sidekiq \d+\.\d+\.\d+/).count
end
end
-
namespace :incoming_email do
desc "GitLab | Check the configuration of Reply by email"
task check: :environment do
@@ -649,7 +640,6 @@ namespace :gitlab do
finished_checking "Reply by email"
end
-
# Checks
########################
@@ -724,8 +714,11 @@ namespace :gitlab do
def check_imap_authentication
print "IMAP server credentials are correct? ... "
- config_path = Rails.root.join('config', 'mail_room.yml')
- config_file = YAML.load(ERB.new(File.read(config_path)).result)
+ config_path = Rails.root.join('config', 'mail_room.yml').to_s
+ erb = ERB.new(File.read(config_path))
+ erb.filename = config_path
+ config_file = YAML.load(erb.result)
+
config = config_file[:mailboxes].first
if config
@@ -754,7 +747,7 @@ namespace :gitlab do
end
def mail_room_running?
- ps_ux, _ = Gitlab::Popen.popen(%W(ps ux))
+ ps_ux, _ = Gitlab::Popen.popen(%w(ps ux))
ps_ux.include?("mail_room")
end
end
@@ -802,13 +795,13 @@ namespace :gitlab do
def check_ldap_auth(adapter)
auth = adapter.config.has_auth?
- if auth && adapter.ldap.bind
- message = 'Success'.color(:green)
- elsif auth
- message = 'Failed. Check `bind_dn` and `password` configuration values'.color(:red)
- else
- message = 'Anonymous. No `bind_dn` or `password` configured'.color(:yellow)
- end
+ message = if auth && adapter.ldap.bind
+ 'Success'.color(:green)
+ elsif auth
+ 'Failed. Check `bind_dn` and `password` configuration values'.color(:red)
+ else
+ 'Anonymous. No `bind_dn` or `password` configured'.color(:yellow)
+ end
puts "LDAP authentication... #{message}"
end
@@ -835,11 +828,11 @@ namespace :gitlab do
user = User.find_by(username: username)
if user
repo_dirs = user.authorized_projects.map do |p|
- File.join(
- p.repository_storage_path,
- "#{p.path_with_namespace}.git"
- )
- end
+ File.join(
+ p.repository_storage_path,
+ "#{p.path_with_namespace}.git"
+ )
+ end
repo_dirs.each { |repo_dir| check_repo_integrity(repo_dir) }
else
@@ -852,7 +845,7 @@ namespace :gitlab do
##########################
def fix_and_rerun
- puts " Please #{"fix the error above"} and rerun the checks.".color(:red)
+ puts " Please fix the error above and rerun the checks.".color(:red)
end
def for_more_information(*sources)
@@ -914,7 +907,7 @@ namespace :gitlab do
def check_ruby_version
required_version = Gitlab::VersionInfo.new(2, 1, 0)
- current_version = Gitlab::VersionInfo.parse(run_command(%W(ruby --version)))
+ current_version = Gitlab::VersionInfo.parse(run_command(%w(ruby --version)))
print "Ruby version >= #{required_version} ? ... "
@@ -985,13 +978,13 @@ namespace :gitlab do
end
def check_config_lock(repo_dir)
- config_exists = File.exist?(File.join(repo_dir,'config.lock'))
+ config_exists = File.exist?(File.join(repo_dir, 'config.lock'))
config_output = config_exists ? 'yes'.color(:red) : 'no'.color(:green)
puts "'config.lock' file exists?".color(:yellow) + " ... #{config_output}"
end
def check_ref_locks(repo_dir)
- lock_files = Dir.glob(File.join(repo_dir,'refs/heads/*.lock'))
+ lock_files = Dir.glob(File.join(repo_dir, 'refs/heads/*.lock'))
if lock_files.present?
puts "Ref lock files exist:".color(:red)
lock_files.each do |lock_file|
diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake
index 967f630ef20..daf7382dd02 100644
--- a/lib/tasks/gitlab/cleanup.rake
+++ b/lib/tasks/gitlab/cleanup.rake
@@ -25,7 +25,6 @@ namespace :gitlab do
end
all_dirs.each do |dir_path|
-
if remove_flag
if FileUtils.rm_rf dir_path
puts "Removed...#{dir_path}".color(:red)
@@ -53,11 +52,11 @@ namespace :gitlab do
IO.popen(%W(find #{repo_root} -mindepth 1 -maxdepth 2 -name *.git)) do |find|
find.each_line do |path|
path.chomp!
- repo_with_namespace = path.
- sub(repo_root, '').
- sub(%r{^/*}, '').
- chomp('.git').
- chomp('.wiki')
+ repo_with_namespace = path
+ .sub(repo_root, '')
+ .sub(%r{^/*}, '')
+ .chomp('.git')
+ .chomp('.wiki')
next if Project.find_by_full_path(repo_with_namespace)
new_path = path + move_suffix
puts path.inspect + ' -> ' + new_path.inspect
diff --git a/lib/tasks/gitlab/db.rake b/lib/tasks/gitlab/db.rake
index 7c96bc864ce..ecf6b6e068b 100644
--- a/lib/tasks/gitlab/db.rake
+++ b/lib/tasks/gitlab/db.rake
@@ -23,7 +23,7 @@ namespace :gitlab do
end
desc 'Drop all tables'
- task :drop_tables => :environment do
+ task drop_tables: :environment do
connection = ActiveRecord::Base.connection
# If MySQL, turn off foreign key checks
@@ -62,9 +62,9 @@ namespace :gitlab do
ref = Shellwords.escape(args[:ref])
- migrations = `git diff #{ref}.. --name-only -- db/migrate`.lines.
- map { |file| Rails.root.join(file.strip).to_s }.
- select { |file| File.file?(file) }
+ migrations = `git diff #{ref}.. --name-only -- db/migrate`.lines
+ .map { |file| Rails.root.join(file.strip).to_s }
+ .select { |file| File.file?(file) }
Gitlab::DowntimeCheck.new.check_and_print(migrations)
end
diff --git a/lib/tasks/gitlab/git.rake b/lib/tasks/gitlab/git.rake
index a67c1fe1f27..cf82134d97e 100644
--- a/lib/tasks/gitlab/git.rake
+++ b/lib/tasks/gitlab/git.rake
@@ -1,6 +1,5 @@
namespace :gitlab do
namespace :git do
-
desc "GitLab | Git | Repack"
task repack: :environment do
failures = perform_git_cmd(%W(#{Gitlab.config.git.bin_path} repack -a --quiet), "Repacking repo")
@@ -50,6 +49,5 @@ namespace :gitlab do
puts "The following repositories reported errors:".color(:red)
failures.each { |f| puts "- #{f}" }
end
-
end
end
diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake
index b4015f5238e..66e7b7685f7 100644
--- a/lib/tasks/gitlab/import.rake
+++ b/lib/tasks/gitlab/import.rake
@@ -46,7 +46,7 @@ namespace :gitlab do
group = Namespace.find_by(path: group_name)
# create group namespace
unless group
- group = Group.new(:name => group_name)
+ group = Group.new(name: group_name)
group.path = group_name
group.owner = user
if group.save
diff --git a/lib/tasks/gitlab/import_export.rake b/lib/tasks/gitlab/import_export.rake
index c2c6031db67..dd1825c8a9e 100644
--- a/lib/tasks/gitlab/import_export.rake
+++ b/lib/tasks/gitlab/import_export.rake
@@ -7,7 +7,7 @@ namespace :gitlab do
desc "GitLab | Display exported DB structure"
task data: :environment do
- puts YAML.load_file(Gitlab::ImportExport.config_file)['project_tree'].to_yaml(:SortKeys => true)
+ puts YAML.load_file(Gitlab::ImportExport.config_file)['project_tree'].to_yaml(SortKeys: true)
end
end
end
diff --git a/lib/tasks/gitlab/info.rake b/lib/tasks/gitlab/info.rake
index f7c831892ee..ae78fe64eb8 100644
--- a/lib/tasks/gitlab/info.rake
+++ b/lib/tasks/gitlab/info.rake
@@ -2,24 +2,23 @@ namespace :gitlab do
namespace :env do
desc "GitLab | Show information about GitLab and its environment"
task info: :environment do
-
# check if there is an RVM environment
- rvm_version = run_and_match(%W(rvm --version), /[\d\.]+/).try(:to_s)
+ rvm_version = run_and_match(%w(rvm --version), /[\d\.]+/).try(:to_s)
# check Ruby version
- ruby_version = run_and_match(%W(ruby --version), /[\d\.p]+/).try(:to_s)
+ ruby_version = run_and_match(%w(ruby --version), /[\d\.p]+/).try(:to_s)
# check Gem version
- gem_version = run_command(%W(gem --version))
+ gem_version = run_command(%w(gem --version))
# check Bundler version
- bunder_version = run_and_match(%W(bundle --version), /[\d\.]+/).try(:to_s)
+ bunder_version = run_and_match(%w(bundle --version), /[\d\.]+/).try(:to_s)
# check Rake version
- rake_version = run_and_match(%W(rake --version), /[\d\.]+/).try(:to_s)
+ rake_version = run_and_match(%w(rake --version), /[\d\.]+/).try(:to_s)
# check redis version
- redis_version = run_and_match(%W(redis-cli --version), /redis-cli (\d+\.\d+\.\d+)/).to_a
+ redis_version = run_and_match(%w(redis-cli --version), /redis-cli (\d+\.\d+\.\d+)/).to_a
puts ""
puts "System information".color(:yellow)
puts "System:\t\t#{os_name || "unknown".color(:red)}"
- puts "Current User:\t#{run_command(%W(whoami))}"
+ puts "Current User:\t#{run_command(%w(whoami))}"
puts "Using RVM:\t#{rvm_version.present? ? "yes".color(:green) : "no"}"
puts "RVM Version:\t#{rvm_version}" if rvm_version.present?
puts "Ruby Version:\t#{ruby_version || "unknown".color(:red)}"
@@ -29,7 +28,6 @@ namespace :gitlab do
puts "Redis Version:\t#{redis_version[1] || "unknown".color(:red)}"
puts "Sidekiq Version:#{Sidekiq::VERSION}"
-
# check database adapter
database_adapter = ActiveRecord::Base.connection.adapter_name.downcase
@@ -54,8 +52,6 @@ namespace :gitlab do
puts "Using Omniauth:\t#{Gitlab.config.omniauth.enabled ? "yes".color(:green) : "no"}"
puts "Omniauth Providers: #{omniauth_providers.join(', ')}" if Gitlab.config.omniauth.enabled
-
-
# check Gitolite version
gitlab_shell_version_file = "#{Gitlab.config.gitlab_shell.hooks_path}/../VERSION"
if File.readable?(gitlab_shell_version_file)
@@ -71,7 +67,6 @@ namespace :gitlab do
end
puts "Hooks:\t\t#{Gitlab.config.gitlab_shell.hooks_path}"
puts "Git:\t\t#{Gitlab.config.git.bin_path}"
-
end
end
end
diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake
index 5a09cd7ce41..dd2fda54e62 100644
--- a/lib/tasks/gitlab/shell.rake
+++ b/lib/tasks/gitlab/shell.rake
@@ -20,10 +20,10 @@ namespace :gitlab do
config = {
user: Gitlab.config.gitlab.user,
gitlab_url: gitlab_url,
- http_settings: {self_signed_cert: false}.stringify_keys,
+ http_settings: { self_signed_cert: false }.stringify_keys,
auth_file: File.join(user_home, ".ssh", "authorized_keys"),
redis: {
- bin: %x{which redis-cli}.chomp,
+ bin: `which redis-cli`.chomp,
namespace: "resque:gitlab"
}.stringify_keys,
log_level: "INFO",
@@ -43,7 +43,7 @@ namespace :gitlab do
File.open("config.yml", "w+") {|f| f.puts config.to_yaml}
# Launch installation process
- system(*%W(bin/install) + repository_storage_paths_args)
+ system(*%w(bin/install) + repository_storage_paths_args)
end
# (Re)create hooks
diff --git a/lib/tasks/gitlab/sidekiq.rake b/lib/tasks/gitlab/sidekiq.rake
index f2e12d85045..6cbc83b8973 100644
--- a/lib/tasks/gitlab/sidekiq.rake
+++ b/lib/tasks/gitlab/sidekiq.rake
@@ -1,9 +1,9 @@
namespace :gitlab do
namespace :sidekiq do
- QUEUE = 'queue:post_receive'
+ QUEUE = 'queue:post_receive'.freeze
desc 'Drop all Sidekiq PostReceive jobs for a given project'
- task :drop_post_receive , [:project] => :environment do |t, args|
+ task :drop_post_receive, [:project] => :environment do |t, args|
unless args.project.present?
abort "Please specify the project you want to drop PostReceive jobs for:\n rake gitlab:sidekiq:drop_post_receive[group/project]"
end
@@ -21,7 +21,7 @@ namespace :gitlab do
# new jobs already. We will repopulate it with the old jobs, skipping the
# ones we want to drop.
dropped = 0
- while (job = redis.lpop(temp_queue)) do
+ while (job = redis.lpop(temp_queue))
if repo_path(job) == project_path
dropped += 1
else
diff --git a/lib/tasks/gitlab/task_helpers.rb b/lib/tasks/gitlab/task_helpers.rb
index e128738b5f8..2a999ad6959 100644
--- a/lib/tasks/gitlab/task_helpers.rb
+++ b/lib/tasks/gitlab/task_helpers.rb
@@ -19,23 +19,20 @@ module Gitlab
# It will primarily use lsb_relase to determine the OS.
# It has fallbacks to Debian, SuSE, OS X and systems running systemd.
def os_name
- os_name = run_command(%W(lsb_release -irs))
- os_name ||= if File.readable?('/etc/system-release')
- File.read('/etc/system-release')
- end
- os_name ||= if File.readable?('/etc/debian_version')
- debian_version = File.read('/etc/debian_version')
- "Debian #{debian_version}"
- end
- os_name ||= if File.readable?('/etc/SuSE-release')
- File.read('/etc/SuSE-release')
- end
- os_name ||= if os_x_version = run_command(%W(sw_vers -productVersion))
- "Mac OS X #{os_x_version}"
- end
- os_name ||= if File.readable?('/etc/os-release')
- File.read('/etc/os-release').match(/PRETTY_NAME=\"(.+)\"/)[1]
- end
+ os_name = run_command(%w(lsb_release -irs))
+ os_name ||=
+ if File.readable?('/etc/system-release')
+ File.read('/etc/system-release')
+ elsif File.readable?('/etc/debian_version')
+ "Debian #{File.read('/etc/debian_version')}"
+ elsif File.readable?('/etc/SuSE-release')
+ File.read('/etc/SuSE-release')
+ elsif os_x_version = run_command(%w(sw_vers -productVersion))
+ "Mac OS X #{os_x_version}"
+ elsif File.readable?('/etc/os-release')
+ File.read('/etc/os-release').match(/PRETTY_NAME=\"(.+)\"/)[1]
+ end
+
os_name.try(:squish!)
end
@@ -104,7 +101,7 @@ module Gitlab
def warn_user_is_not_gitlab
unless @warned_user_not_gitlab
gitlab_user = Gitlab.config.gitlab.user
- current_user = run_command(%W(whoami)).chomp
+ current_user = run_command(%w(whoami)).chomp
unless current_user == gitlab_user
puts " Warning ".color(:black).background(:yellow)
puts " You are running as user #{current_user.color(:magenta)}, we hope you know what you are doing."
@@ -171,14 +168,14 @@ module Gitlab
def reset_to_tag(tag_wanted, target_dir)
tag =
- begin
- # First try to checkout without fetching
- # to avoid stalling tests if the Internet is down.
- run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} describe -- #{tag_wanted}])
- rescue Gitlab::TaskFailedError
- run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} fetch origin])
- run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} describe -- origin/#{tag_wanted}])
- end
+ begin
+ # First try to checkout without fetching
+ # to avoid stalling tests if the Internet is down.
+ run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} describe -- #{tag_wanted}])
+ rescue Gitlab::TaskFailedError
+ run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} fetch origin])
+ run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} describe -- origin/#{tag_wanted}])
+ end
if tag
run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} reset --hard #{tag.strip}])
diff --git a/lib/tasks/gitlab/test.rake b/lib/tasks/gitlab/test.rake
index 84810b489ce..523b0fa055b 100644
--- a/lib/tasks/gitlab/test.rake
+++ b/lib/tasks/gitlab/test.rake
@@ -2,15 +2,15 @@ namespace :gitlab do
desc "GitLab | Run all tests"
task :test do
cmds = [
- %W(rake brakeman),
- %W(rake rubocop),
- %W(rake spinach),
- %W(rake spec),
- %W(rake karma)
+ %w(rake brakeman),
+ %w(rake rubocop),
+ %w(rake spinach),
+ %w(rake spec),
+ %w(rake karma)
]
cmds.each do |cmd|
- system({'RAILS_ENV' => 'test', 'force' => 'yes'}, *cmd) or raise("#{cmd} failed!")
+ system({ 'RAILS_ENV' => 'test', 'force' => 'yes' }, *cmd) || raise("#{cmd} failed!")
end
end
end
diff --git a/lib/tasks/gitlab/track_deployment.rake b/lib/tasks/gitlab/track_deployment.rake
index 84aa2e8507a..6f101aea303 100644
--- a/lib/tasks/gitlab/track_deployment.rake
+++ b/lib/tasks/gitlab/track_deployment.rake
@@ -1,8 +1,8 @@
namespace :gitlab do
desc 'GitLab | Tracks a deployment in GitLab Performance Monitoring'
task track_deployment: :environment do
- metric = Gitlab::Metrics::Metric.
- new('deployments', version: Gitlab::VERSION)
+ metric = Gitlab::Metrics::Metric
+ .new('deployments', version: Gitlab::VERSION)
Gitlab::Metrics.submit_metrics([metric.to_hash])
end
diff --git a/lib/tasks/gitlab/update_templates.rake b/lib/tasks/gitlab/update_templates.rake
index b77a5bb62d1..dbdfb335a5c 100644
--- a/lib/tasks/gitlab/update_templates.rake
+++ b/lib/tasks/gitlab/update_templates.rake
@@ -46,7 +46,7 @@ namespace :gitlab do
"https://gitlab.com/gitlab-org/gitlab-ci-yml.git",
/(\.{1,2}|LICENSE|Pages|autodeploy|\.gitlab-ci.yml)\z/
)
- ]
+ ].freeze
def vendor_directory
Rails.root.join('vendor')
diff --git a/lib/tasks/gitlab/web_hook.rake b/lib/tasks/gitlab/web_hook.rake
index 49530e7a372..5a1c8006052 100644
--- a/lib/tasks/gitlab/web_hook.rake
+++ b/lib/tasks/gitlab/web_hook.rake
@@ -1,7 +1,7 @@
namespace :gitlab do
namespace :web_hook do
desc "GitLab | Adds a webhook to the projects"
- task :add => :environment do
+ task add: :environment do
web_hook_url = ENV['URL']
namespace_path = ENV['NAMESPACE']
@@ -21,7 +21,7 @@ namespace :gitlab do
end
desc "GitLab | Remove a webhook from the projects"
- task :rm => :environment do
+ task rm: :environment do
web_hook_url = ENV['URL']
namespace_path = ENV['NAMESPACE']
@@ -34,7 +34,7 @@ namespace :gitlab do
end
desc "GitLab | List webhooks"
- task :list => :environment do
+ task list: :environment do
namespace_path = ENV['NAMESPACE']
projects = find_projects(namespace_path)
diff --git a/lib/tasks/karma.rake b/lib/tasks/karma.rake
index 89812a179ec..40465ea3bf0 100644
--- a/lib/tasks/karma.rake
+++ b/lib/tasks/karma.rake
@@ -1,6 +1,4 @@
unless Rails.env.production?
- Rake::Task['karma'].clear if Rake::Task.task_defined?('karma')
-
namespace :karma do
desc 'GitLab | Karma | Generate fixtures for JavaScript tests'
RSpec::Core::RakeTask.new(:fixtures) do |t|
@@ -10,16 +8,13 @@ unless Rails.env.production?
end
desc 'GitLab | Karma | Run JavaScript tests'
- task :tests do
- sh "npm run karma" do |ok, res|
+ task tests: ['yarn:check'] do
+ sh "yarn run karma" do |ok, res|
abort('rake karma:tests failed') unless ok
end
end
end
desc 'GitLab | Karma | Shortcut for karma:fixtures and karma:tests'
- task :karma do
- Rake::Task['karma:fixtures'].invoke
- Rake::Task['karma:tests'].invoke
- end
+ task karma: ['karma:fixtures', 'karma:tests']
end
diff --git a/lib/tasks/lint.rake b/lib/tasks/lint.rake
index 32b668df3bf..7b63e93db0e 100644
--- a/lib/tasks/lint.rake
+++ b/lib/tasks/lint.rake
@@ -6,4 +6,3 @@ unless Rails.env.production?
end
end
end
-
diff --git a/lib/tasks/migrate/migrate_iids.rake b/lib/tasks/migrate/migrate_iids.rake
index 4f2486157b7..fc2cea8c016 100644
--- a/lib/tasks/migrate/migrate_iids.rake
+++ b/lib/tasks/migrate/migrate_iids.rake
@@ -24,7 +24,7 @@ task migrate_iids: :environment do
else
print 'F'
end
- rescue => ex
+ rescue
print 'F'
end
end
diff --git a/lib/tasks/services.rake b/lib/tasks/services.rake
index 39541c0b9c6..56b81106c5f 100644
--- a/lib/tasks/services.rake
+++ b/lib/tasks/services.rake
@@ -76,23 +76,23 @@ namespace :services do
end
param_hash
- end.sort_by { |p| p[:required] ? 0 : 1 }
+ end
+ service_hash[:params].sort_by! { |p| p[:required] ? 0 : 1 }
- puts "Collected data for: #{service.title}, #{Time.now-service_start}"
+ puts "Collected data for: #{service.title}, #{Time.now - service_start}"
service_hash
end
doc_start = Time.now
doc_path = File.join(Rails.root, 'doc', 'api', 'services.md')
- result = ERB.new(services_template, 0 , '>')
+ result = ERB.new(services_template, 0, '>')
.result(OpenStruct.new(services: services).instance_eval { binding })
File.open(doc_path, 'w') do |f|
f.write result
end
- puts "write a new service.md to: #{doc_path.to_s}, #{Time.now-doc_start}"
-
+ puts "write a new service.md to: #{doc_path}, #{Time.now - doc_start}"
end
end
diff --git a/lib/tasks/sidekiq.rake b/lib/tasks/sidekiq.rake
index d1f6ed87704..dd9ce86f7ca 100644
--- a/lib/tasks/sidekiq.rake
+++ b/lib/tasks/sidekiq.rake
@@ -1,21 +1,21 @@
namespace :sidekiq do
desc "GitLab | Stop sidekiq"
task :stop do
- system *%W(bin/background_jobs stop)
+ system(*%w(bin/background_jobs stop))
end
desc "GitLab | Start sidekiq"
task :start do
- system *%W(bin/background_jobs start)
+ system(*%w(bin/background_jobs start))
end
desc 'GitLab | Restart sidekiq'
task :restart do
- system *%W(bin/background_jobs restart)
+ system(*%w(bin/background_jobs restart))
end
desc "GitLab | Start sidekiq with launchd on Mac OS X"
task :launchd do
- system *%W(bin/background_jobs start_no_deamonize)
+ system(*%w(bin/background_jobs start_no_deamonize))
end
end
diff --git a/lib/tasks/spec.rake b/lib/tasks/spec.rake
index 2cf7a25a0fd..602c60be828 100644
--- a/lib/tasks/spec.rake
+++ b/lib/tasks/spec.rake
@@ -4,8 +4,8 @@ namespace :spec do
desc 'GitLab | Rspec | Run request specs'
task :api do
cmds = [
- %W(rake gitlab:setup),
- %W(rspec spec --tag @api)
+ %w(rake gitlab:setup),
+ %w(rspec spec --tag @api)
]
run_commands(cmds)
end
@@ -13,8 +13,8 @@ namespace :spec do
desc 'GitLab | Rspec | Run feature specs'
task :feature do
cmds = [
- %W(rake gitlab:setup),
- %W(rspec spec --tag @feature)
+ %w(rake gitlab:setup),
+ %w(rspec spec --tag @feature)
]
run_commands(cmds)
end
@@ -22,8 +22,8 @@ namespace :spec do
desc 'GitLab | Rspec | Run model specs'
task :models do
cmds = [
- %W(rake gitlab:setup),
- %W(rspec spec --tag @models)
+ %w(rake gitlab:setup),
+ %w(rspec spec --tag @models)
]
run_commands(cmds)
end
@@ -31,8 +31,8 @@ namespace :spec do
desc 'GitLab | Rspec | Run service specs'
task :services do
cmds = [
- %W(rake gitlab:setup),
- %W(rspec spec --tag @services)
+ %w(rake gitlab:setup),
+ %w(rspec spec --tag @services)
]
run_commands(cmds)
end
@@ -40,8 +40,8 @@ namespace :spec do
desc 'GitLab | Rspec | Run lib specs'
task :lib do
cmds = [
- %W(rake gitlab:setup),
- %W(rspec spec --tag @lib)
+ %w(rake gitlab:setup),
+ %w(rspec spec --tag @lib)
]
run_commands(cmds)
end
@@ -49,8 +49,8 @@ namespace :spec do
desc 'GitLab | Rspec | Run other specs'
task :other do
cmds = [
- %W(rake gitlab:setup),
- %W(rspec spec --tag ~@api --tag ~@feature --tag ~@models --tag ~@lib --tag ~@services)
+ %w(rake gitlab:setup),
+ %w(rspec spec --tag ~@api --tag ~@feature --tag ~@models --tag ~@lib --tag ~@services)
]
run_commands(cmds)
end
@@ -59,14 +59,14 @@ end
desc "GitLab | Run specs"
task :spec do
cmds = [
- %W(rake gitlab:setup),
- %W(rspec spec),
+ %w(rake gitlab:setup),
+ %w(rspec spec),
]
run_commands(cmds)
end
def run_commands(cmds)
cmds.each do |cmd|
- system({'RAILS_ENV' => 'test', 'force' => 'yes'}, *cmd) or raise("#{cmd} failed!")
+ system({ 'RAILS_ENV' => 'test', 'force' => 'yes' }, *cmd) || raise("#{cmd} failed!")
end
end
diff --git a/lib/tasks/spinach.rake b/lib/tasks/spinach.rake
index 8dbfa7751dc..19ff13f06c0 100644
--- a/lib/tasks/spinach.rake
+++ b/lib/tasks/spinach.rake
@@ -35,7 +35,7 @@ task :spinach do
end
def run_system_command(cmd)
- system({'RAILS_ENV' => 'test', 'force' => 'yes'}, *cmd)
+ system({ 'RAILS_ENV' => 'test', 'force' => 'yes' }, *cmd)
end
def run_spinach_command(args)
diff --git a/lib/tasks/yarn.rake b/lib/tasks/yarn.rake
new file mode 100644
index 00000000000..2ac88a039e7
--- /dev/null
+++ b/lib/tasks/yarn.rake
@@ -0,0 +1,40 @@
+
+namespace :yarn do
+ desc 'Ensure Yarn is installed'
+ task :available do
+ unless system('yarn --version', out: File::NULL)
+ warn(
+ 'Error: Yarn executable was not detected in the system.'.color(:red),
+ 'Download Yarn at https://yarnpkg.com/en/docs/install'.color(:green)
+ )
+ abort
+ end
+ end
+
+ desc 'Ensure Node dependencies are installed'
+ task check: ['yarn:available'] do
+ unless system('yarn check --ignore-engines', out: File::NULL)
+ warn(
+ 'Error: You have unmet dependencies. (`yarn check` command failed)'.color(:red),
+ 'Run `yarn install` to install missing modules.'.color(:green)
+ )
+ abort
+ end
+ end
+
+ desc 'Install Node dependencies with Yarn'
+ task install: ['yarn:available'] do
+ unless system('yarn install --pure-lockfile --ignore-engines')
+ abort 'Error: Unable to install node modules.'.color(:red)
+ end
+ end
+
+ desc 'Remove Node dependencies'
+ task :clobber do
+ warn 'Purging ./node_modules directory'.color(:red)
+ FileUtils.rm_rf 'node_modules'
+ end
+end
+
+desc 'Install Node dependencies with Yarn'
+task yarn: ['yarn:install']
diff --git a/package.json b/package.json
index 9581d966237..66aa7e9fe5d 100644
--- a/package.json
+++ b/package.json
@@ -3,49 +3,64 @@
"scripts": {
"dev-server": "webpack-dev-server --config config/webpack.config.js",
"eslint": "eslint --max-warnings 0 --ext .js,.js.es6 .",
- "eslint-fix": "npm run eslint -- --fix",
- "eslint-report": "npm run eslint -- --format html --output-file ./eslint-report.html",
+ "eslint-fix": "eslint --max-warnings 0 --ext .js,.js.es6 --fix .",
+ "eslint-report": "eslint --max-warnings 0 --ext .js,.js.es6 --format html --output-file ./eslint-report.html .",
"karma": "karma start config/karma.config.js --single-run",
"karma-start": "karma start config/karma.config.js",
"webpack": "webpack --config config/webpack.config.js",
- "webpack-prod": "NODE_ENV=production npm run webpack"
+ "webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js"
},
"dependencies": {
- "babel": "^5.8.38",
- "babel-core": "^5.8.38",
- "babel-loader": "^5.4.2",
- "bootstrap-sass": "3.3.6",
+ "babel-core": "^6.22.1",
+ "babel-loader": "^6.2.10",
+ "babel-preset-es2015": "^6.22.0",
+ "babel-preset-stage-2": "^6.22.0",
+ "bootstrap-sass": "^3.3.6",
"compression-webpack-plugin": "^0.3.2",
- "d3": "3.5.11",
- "dropzone": "4.2.0",
- "exports-loader": "^0.6.3",
- "imports-loader": "^0.6.5",
- "jquery": "2.2.1",
- "jquery-ui": "github:jquery/jquery-ui#1.11.4",
- "jquery-ujs": "1.2.1",
- "json-loader": "^0.5.4",
- "mousetrap": "1.4.6",
+ "d3": "^3.5.11",
+ "dropzone": "^4.2.0",
+ "es6-promise": "^4.0.5",
+ "jquery": "^2.2.1",
+ "jquery-ui": "git+https://github.com/jquery/jquery-ui#1.11.4",
+ "jquery-ujs": "^1.2.1",
+ "js-cookie": "^2.1.3",
+ "mousetrap": "^1.4.6",
+ "pikaday": "^1.5.1",
"select2": "3.5.2-browserify",
- "stats-webpack-plugin": "^0.4.2",
- "underscore": "1.8.3",
- "vue": "2.0.3",
- "vue-resource": "0.9.3",
- "webpack": "^1.14.0",
- "webpack-dev-server": "^1.16.2"
+ "stats-webpack-plugin": "^0.4.3",
+ "timeago.js": "^2.0.5",
+ "underscore": "^1.8.3",
+ "vue": "^2.0.3",
+ "vue-resource": "^0.9.3",
+ "webpack": "^2.2.1",
+ "webpack-bundle-analyzer": "^2.3.0"
},
"devDependencies": {
+ "babel-plugin-istanbul": "^4.0.0",
"eslint": "^3.10.1",
"eslint-config-airbnb-base": "^10.0.1",
+ "eslint-import-resolver-webpack": "^0.8.1",
"eslint-plugin-filenames": "^1.1.0",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-jasmine": "^2.1.0",
"istanbul": "^0.4.5",
"jasmine-core": "^2.5.2",
"jasmine-jquery": "^2.1.1",
- "karma": "^1.3.0",
+ "karma": "^1.4.1",
+ "karma-coverage-istanbul-reporter": "^0.2.0",
"karma-jasmine": "^1.1.0",
+ "karma-mocha-reporter": "^2.2.2",
"karma-phantomjs-launcher": "^1.0.2",
"karma-sourcemap-loader": "^0.3.7",
- "karma-webpack": "^1.8.0"
+ "karma-webpack": "^2.0.2",
+ "webpack-dev-server": "^2.3.0"
+ },
+ "nyc": {
+ "exclude": [
+ "spec/javascripts/test_bundle.js",
+ "spec/javascripts/**/*_spec.js",
+ "spec/javascripts/**/*_spec.js.es6",
+ "app/assets/javascripts/droplab/**/*"
+ ]
}
}
diff --git a/rubocop/cop/gem_fetcher.rb b/rubocop/cop/gem_fetcher.rb
index 4a63c760744..e157d8e0791 100644
--- a/rubocop/cop/gem_fetcher.rb
+++ b/rubocop/cop/gem_fetcher.rb
@@ -1,17 +1,15 @@
module RuboCop
module Cop
- # Cop that checks for all gems specified in the Gemfile, and will
- # alert if any gem is to be fetched not from the RubyGems index.
- # This enforcement is done so as to minimize external build
- # dependencies and build times.
+ # This cop prevents usage of the `git` and `github` arguments to `gem` in a
+ # `Gemfile` in order to avoid additional points of failure beyond
+ # rubygems.org.
class GemFetcher < RuboCop::Cop::Cop
- MSG = 'Do not use gems from git repositories, only use gems from RubyGems.'
+ MSG = 'Do not use gems from git repositories, only use gems from RubyGems.'.freeze
- GIT_KEYS = [:git, :github]
+ GIT_KEYS = [:git, :github].freeze
def on_send(node)
- file_path = node.location.expression.source_buffer.name
- return unless file_path.end_with?("Gemfile")
+ return unless gemfile?(node)
func_name = node.children[1]
return unless func_name == :gem
@@ -19,10 +17,21 @@ module RuboCop
node.children.last.each_node(:pair) do |pair|
key_name = pair.children[0].children[0].to_sym
if GIT_KEYS.include?(key_name)
- add_offense(node, :selector)
+ add_offense(node, pair.source_range, MSG)
end
end
end
+
+ private
+
+ def gemfile?(node)
+ node
+ .location
+ .expression
+ .source_buffer
+ .name
+ .end_with?("Gemfile")
+ end
end
end
end
diff --git a/rubocop/cop/migration/column_with_default.rb b/rubocop/cop/migration/add_column.rb
index 97ee8b11044..d2cf36c454a 100644
--- a/rubocop/cop/migration/column_with_default.rb
+++ b/rubocop/cop/migration/add_column.rb
@@ -1,15 +1,17 @@
+require_relative '../../migration_helpers'
+
module RuboCop
module Cop
module Migration
# Cop that checks if columns are added in a way that doesn't require
# downtime.
- class ColumnWithDefault < RuboCop::Cop::Cop
+ class AddColumn < RuboCop::Cop::Cop
include MigrationHelpers
- WHITELISTED_TABLES = [:application_settings]
+ WHITELISTED_TABLES = [:application_settings].freeze
- MSG = 'add_column with a default value requires downtime, ' \
- 'use add_column_with_default instead'
+ MSG = '`add_column` with a default value requires downtime, ' \
+ 'use `add_column_with_default` instead'.freeze
def on_send(node)
return unless in_migration?(node)
diff --git a/rubocop/cop/migration/add_column_with_default.rb b/rubocop/cop/migration/add_column_with_default.rb
new file mode 100644
index 00000000000..54a920d4b49
--- /dev/null
+++ b/rubocop/cop/migration/add_column_with_default.rb
@@ -0,0 +1,34 @@
+require_relative '../../migration_helpers'
+
+module RuboCop
+ module Cop
+ module Migration
+ # Cop that checks if `add_column_with_default` is used with `up`/`down` methods
+ # and not `change`.
+ class AddColumnWithDefault < RuboCop::Cop::Cop
+ include MigrationHelpers
+
+ MSG = '`add_column_with_default` is not reversible so you must manually define ' \
+ 'the `up` and `down` methods in your migration class, using `remove_column` in `down`'.freeze
+
+ def on_send(node)
+ return unless in_migration?(node)
+
+ name = node.children[1]
+
+ return unless name == :add_column_with_default
+
+ node.each_ancestor(:def) do |def_node|
+ next unless method_name(def_node) == :change
+
+ add_offense(def_node, :name)
+ end
+ end
+
+ def method_name(node)
+ node.children.first
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/cop/migration/add_concurrent_foreign_key.rb b/rubocop/cop/migration/add_concurrent_foreign_key.rb
new file mode 100644
index 00000000000..d1fc94d55be
--- /dev/null
+++ b/rubocop/cop/migration/add_concurrent_foreign_key.rb
@@ -0,0 +1,27 @@
+require_relative '../../migration_helpers'
+
+module RuboCop
+ module Cop
+ module Migration
+ # Cop that checks if `add_concurrent_foreign_key` is used instead of
+ # `add_foreign_key`.
+ class AddConcurrentForeignKey < RuboCop::Cop::Cop
+ include MigrationHelpers
+
+ MSG = '`add_foreign_key` requires downtime, use `add_concurrent_foreign_key` instead'.freeze
+
+ def on_send(node)
+ return unless in_migration?(node)
+
+ name = node.children[1]
+
+ add_offense(node, :selector) if name == :add_foreign_key
+ end
+
+ def method_name(node)
+ node.children.first
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/cop/migration/add_index.rb b/rubocop/cop/migration/add_index.rb
index d9247a1f7ea..fa21a0d6555 100644
--- a/rubocop/cop/migration/add_index.rb
+++ b/rubocop/cop/migration/add_index.rb
@@ -1,3 +1,5 @@
+require_relative '../../migration_helpers'
+
module RuboCop
module Cop
module Migration
@@ -5,7 +7,7 @@ module RuboCop
class AddIndex < RuboCop::Cop::Cop
include MigrationHelpers
- MSG = 'add_index requires downtime, use add_concurrent_index instead'
+ MSG = '`add_index` requires downtime, use `add_concurrent_index` instead'.freeze
def on_def(node)
return unless in_migration?(node)
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index 7f20754ee51..d4266d0deae 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -1,4 +1,5 @@
-require_relative 'migration_helpers'
-require_relative 'cop/migration/add_index'
-require_relative 'cop/migration/column_with_default'
require_relative 'cop/gem_fetcher'
+require_relative 'cop/migration/add_column'
+require_relative 'cop/migration/add_column_with_default'
+require_relative 'cop/migration/add_concurrent_foreign_key'
+require_relative 'cop/migration/add_index'
diff --git a/spec/config/mail_room_spec.rb b/spec/config/mail_room_spec.rb
index 294fae95752..0b8ff006d22 100644
--- a/spec/config/mail_room_spec.rb
+++ b/spec/config/mail_room_spec.rb
@@ -8,7 +8,7 @@ describe 'mail_room.yml' do
context 'when incoming email is disabled' do
before do
- ENV['MAIL_ROOM_GITLAB_CONFIG_FILE'] = Rails.root.join('spec/fixtures/mail_room_disabled.yml').to_s
+ ENV['MAIL_ROOM_GITLAB_CONFIG_FILE'] = Rails.root.join('spec/fixtures/config/mail_room_disabled.yml').to_s
Gitlab::MailRoom.reset_config!
end
@@ -26,7 +26,7 @@ describe 'mail_room.yml' do
let(:gitlab_redis) { Gitlab::Redis.new(Rails.env) }
before do
- ENV['MAIL_ROOM_GITLAB_CONFIG_FILE'] = Rails.root.join('spec/fixtures/mail_room_enabled.yml').to_s
+ ENV['MAIL_ROOM_GITLAB_CONFIG_FILE'] = Rails.root.join('spec/fixtures/config/mail_room_enabled.yml').to_s
Gitlab::MailRoom.reset_config!
end
diff --git a/spec/controllers/admin/runners_controller_spec.rb b/spec/controllers/admin/runners_controller_spec.rb
new file mode 100644
index 00000000000..b5fe40d0510
--- /dev/null
+++ b/spec/controllers/admin/runners_controller_spec.rb
@@ -0,0 +1,85 @@
+require 'spec_helper'
+
+describe Admin::RunnersController do
+ let(:runner) { create(:ci_runner) }
+
+ before do
+ sign_in(create(:admin))
+ end
+
+ describe '#index' do
+ it 'lists all runners' do
+ get :index
+
+ expect(response).to have_http_status(200)
+ end
+ end
+
+ describe '#show' do
+ it 'shows a particular runner' do
+ get :show, id: runner.id
+
+ expect(response).to have_http_status(200)
+ end
+
+ it 'shows 404 for unknown runner' do
+ get :show, id: 0
+
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ describe '#update' do
+ it 'updates the runner and ticks the queue' do
+ new_desc = runner.description.swapcase
+
+ expect do
+ post :update, id: runner.id, runner: { description: new_desc }
+ end.to change { runner.ensure_runner_queue_value }
+
+ runner.reload
+
+ expect(response).to have_http_status(302)
+ expect(runner.description).to eq(new_desc)
+ end
+ end
+
+ describe '#destroy' do
+ it 'destroys the runner' do
+ delete :destroy, id: runner.id
+
+ expect(response).to have_http_status(302)
+ expect(Ci::Runner.find_by(id: runner.id)).to be_nil
+ end
+ end
+
+ describe '#resume' do
+ it 'marks the runner as active and ticks the queue' do
+ runner.update(active: false)
+
+ expect do
+ post :resume, id: runner.id
+ end.to change { runner.ensure_runner_queue_value }
+
+ runner.reload
+
+ expect(response).to have_http_status(302)
+ expect(runner.active).to eq(true)
+ end
+ end
+
+ describe '#pause' do
+ it 'marks the runner as inactive and ticks the queue' do
+ runner.update(active: true)
+
+ expect do
+ post :pause, id: runner.id
+ end.to change { runner.ensure_runner_queue_value }
+
+ runner.reload
+
+ expect(response).to have_http_status(302)
+ expect(runner.active).to eq(false)
+ end
+ end
+end
diff --git a/spec/controllers/blob_controller_spec.rb b/spec/controllers/blob_controller_spec.rb
index 2fcb4a6a528..44e011fd3a8 100644
--- a/spec/controllers/blob_controller_spec.rb
+++ b/spec/controllers/blob_controller_spec.rb
@@ -19,8 +19,8 @@ describe Projects::BlobController do
before do
get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
id: id)
end
@@ -50,8 +50,8 @@ describe Projects::BlobController do
before do
get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
id: id)
controller.instance_variable_set(:@blob, nil)
end
diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb
index 79ef3a1adad..7072bd5e87c 100644
--- a/spec/controllers/dashboard/todos_controller_spec.rb
+++ b/spec/controllers/dashboard/todos_controller_spec.rb
@@ -1,16 +1,19 @@
require 'spec_helper'
describe Dashboard::TodosController do
+ include ApiHelpers
+
let(:user) { create(:user) }
+ let(:author) { create(:user) }
let(:project) { create(:empty_project) }
let(:todo_service) { TodoService.new }
- describe 'GET #index' do
- before do
- sign_in(user)
- project.team << [user, :developer]
- end
+ before do
+ sign_in(user)
+ project.team << [user, :developer]
+ end
+ describe 'GET #index' do
context 'when using pagination' do
let(:last_page) { user.todos.page.total_pages }
let!(:issues) { create_list(:issue, 2, project: project, assignee: user) }
@@ -34,4 +37,16 @@ describe Dashboard::TodosController do
end
end
end
+
+ describe 'PATCH #restore' do
+ let(:todo) { create(:todo, :done, user: user, project: project, author: author) }
+
+ it 'restores the todo to pending state' do
+ patch :restore, id: todo.id
+
+ expect(todo.reload).to be_pending
+ expect(response).to have_http_status(200)
+ expect(json_response).to eq({ "count" => "1", "done_count" => "0" })
+ end
+ end
end
diff --git a/spec/controllers/dashboard_controller_spec.rb b/spec/controllers/dashboard_controller_spec.rb
new file mode 100644
index 00000000000..566d8515198
--- /dev/null
+++ b/spec/controllers/dashboard_controller_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe DashboardController do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+
+ before do
+ project.team << [user, :master]
+ sign_in(user)
+ end
+
+ describe 'GET issues' do
+ it_behaves_like 'issuables list meta-data', :issue, :issues
+ end
+
+ describe 'GET merge requests' do
+ it_behaves_like 'issuables list meta-data', :merge_request, :merge_requests
+ end
+end
diff --git a/spec/controllers/profiles/keys_controller_spec.rb b/spec/controllers/profiles/keys_controller_spec.rb
index 6bcfae0fc13..f7219690722 100644
--- a/spec/controllers/profiles/keys_controller_spec.rb
+++ b/spec/controllers/profiles/keys_controller_spec.rb
@@ -42,10 +42,9 @@ describe Profiles::KeysController do
end
describe "user with keys" do
- before do
- user.keys << create(:key)
- user.keys << create(:another_key)
- end
+ let!(:key) { create(:key, user: user) }
+ let!(:another_key) { create(:another_key, user: user) }
+ let!(:deploy_key) { create(:deploy_key, user: user) }
it "does generally work" do
get :get_keys, username: user.username
@@ -53,16 +52,16 @@ describe Profiles::KeysController do
expect(response).to be_success
end
- it "renders all keys separated with a new line" do
+ it "renders all non deploy keys separated with a new line" do
get :get_keys, username: user.username
- expect(response.body).not_to eq("")
+ expect(response.body).not_to eq('')
expect(response.body).to eq(user.all_ssh_keys.join("\n"))
- # Unique part of key 1
- expect(response.body).to match(/PWx6WM4lhHNedGfBpPJNPpZ/)
- # Key 2
- expect(response.body).to match(/AQDmTillFzNTrrGgwaCKaSj/)
+ expect(response.body).to include(key.key.sub(' dummy@gitlab.com', ''))
+ expect(response.body).to include(another_key.key)
+
+ expect(response.body).not_to include(deploy_key.key)
end
it "does not render the comment of the key" do
diff --git a/spec/controllers/profiles/notifications_controller_spec.rb b/spec/controllers/profiles/notifications_controller_spec.rb
new file mode 100644
index 00000000000..58caf7999cf
--- /dev/null
+++ b/spec/controllers/profiles/notifications_controller_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+describe Profiles::NotificationsController do
+ let(:user) do
+ create(:user) do |user|
+ user.emails.create(email: 'original@example.com')
+ user.emails.create(email: 'new@example.com')
+ user.update(notification_email: 'original@example.com')
+ user.save!
+ end
+ end
+
+ describe 'GET show' do
+ it 'renders' do
+ sign_in(user)
+
+ get :show
+
+ expect(response).to render_template :show
+ end
+ end
+
+ describe 'POST update' do
+ it 'updates only permitted attributes' do
+ sign_in(user)
+
+ put :update, user: { notification_email: 'new@example.com', notified_of_own_activity: true, admin: true }
+
+ user.reload
+ expect(user.notification_email).to eq('new@example.com')
+ expect(user.notified_of_own_activity).to eq(true)
+ expect(user.admin).to eq(false)
+ expect(controller).to set_flash[:notice].to('Notification settings saved')
+ end
+
+ it 'shows an error message if the params are invalid' do
+ sign_in(user)
+
+ put :update, user: { notification_email: '' }
+
+ expect(user.reload.notification_email).to eq('original@example.com')
+ expect(controller).to set_flash[:alert].to('Failed to save new settings')
+ end
+ end
+end
diff --git a/spec/controllers/profiles/personal_access_tokens_spec.rb b/spec/controllers/profiles/personal_access_tokens_spec.rb
index 45534a3a587..9d5f4c99f6d 100644
--- a/spec/controllers/profiles/personal_access_tokens_spec.rb
+++ b/spec/controllers/profiles/personal_access_tokens_spec.rb
@@ -32,10 +32,10 @@ describe Profiles::PersonalAccessTokensController do
context "scopes" do
it "allows creation of a token with scopes" do
- post :create, personal_access_token: { name: FFaker::Product.brand, scopes: ['api', 'read_user'] }
+ post :create, personal_access_token: { name: FFaker::Product.brand, scopes: %w(api read_user) }
expect(created_token).not_to be_nil
- expect(created_token.scopes).to eq(['api', 'read_user'])
+ expect(created_token.scopes).to eq(%w(api read_user))
end
it "allows creation of a token with no scopes" do
diff --git a/spec/controllers/profiles/preferences_controller_spec.rb b/spec/controllers/profiles/preferences_controller_spec.rb
index 8f02003992a..7b3aa0491c7 100644
--- a/spec/controllers/profiles/preferences_controller_spec.rb
+++ b/spec/controllers/profiles/preferences_controller_spec.rb
@@ -25,8 +25,7 @@ describe Profiles::PreferencesController do
def go(params: {}, format: :js)
params.reverse_merge!(
color_scheme_id: '1',
- dashboard: 'stars',
- theme_id: '1'
+ dashboard: 'stars'
)
patch :update, user: params, format: format
@@ -41,8 +40,7 @@ describe Profiles::PreferencesController do
it "changes the user's preferences" do
prefs = {
color_scheme_id: '1',
- dashboard: 'stars',
- theme_id: '2'
+ dashboard: 'stars'
}.with_indifferent_access
expect(user).to receive(:update_attributes).with(prefs)
diff --git a/spec/controllers/projects/blame_controller_spec.rb b/spec/controllers/projects/blame_controller_spec.rb
index addc5e7ec33..c086b386381 100644
--- a/spec/controllers/projects/blame_controller_spec.rb
+++ b/spec/controllers/projects/blame_controller_spec.rb
@@ -16,8 +16,8 @@ describe Projects::BlameController do
before do
get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
id: id)
end
diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb
index b36d0e69330..ec36a64b415 100644
--- a/spec/controllers/projects/blob_controller_spec.rb
+++ b/spec/controllers/projects/blob_controller_spec.rb
@@ -14,8 +14,8 @@ describe Projects::BlobController do
render_views
def do_get(opts = {})
- params = { namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ params = { namespace_id: project.namespace,
+ project_id: project,
id: 'master/CHANGELOG' }
get :diff, params.merge(opts)
end
@@ -40,8 +40,8 @@ describe Projects::BlobController do
describe 'PUT update' do
let(:default_params) do
{
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
id: 'master/CHANGELOG',
target_branch: 'master',
content: 'Added changes',
@@ -86,32 +86,47 @@ describe Projects::BlobController do
end
context 'when user has forked project' do
- let(:guest) { create(:user) }
- let!(:forked_project) { Projects::ForkService.new(project, guest).execute }
- let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, source_branch: "fork-test-1", target_branch: "master") }
-
- before { sign_in(guest) }
-
- it "redirects to forked project new merge request" do
- default_params[:target_branch] = "fork-test-1"
- default_params[:create_merge_request] = 1
-
- allow_any_instance_of(Files::UpdateService).to receive(:commit).and_return(:success)
-
- put :update, default_params
-
- expect(response).to redirect_to(
- new_namespace_project_merge_request_path(
- forked_project.namespace,
- forked_project,
- merge_request: {
- source_project_id: forked_project.id,
- target_project_id: project.id,
- source_branch: "fork-test-1",
- target_branch: "master"
- }
+ let(:forked_project_link) { create(:forked_project_link, forked_from_project: project) }
+ let!(:forked_project) { forked_project_link.forked_to_project }
+ let(:guest) { forked_project.owner }
+
+ before do
+ sign_in(guest)
+ end
+
+ context 'when editing on the fork' do
+ before do
+ default_params[:namespace_id] = forked_project.namespace
+ default_params[:project_id] = forked_project
+ end
+
+ it 'redirects to blob' do
+ put :update, default_params
+
+ expect(response).to redirect_to(namespace_project_blob_path(forked_project.namespace, forked_project, 'master/CHANGELOG'))
+ end
+ end
+
+ context 'when editing on the original repository' do
+ it "redirects to forked project new merge request" do
+ default_params[:target_branch] = "fork-test-1"
+ default_params[:create_merge_request] = 1
+
+ put :update, default_params
+
+ expect(response).to redirect_to(
+ new_namespace_project_merge_request_path(
+ forked_project.namespace,
+ forked_project,
+ merge_request: {
+ source_project_id: forked_project.id,
+ target_project_id: project.id,
+ source_branch: "fork-test-1",
+ target_branch: "master"
+ }
+ )
)
- )
+ end
end
end
end
diff --git a/spec/controllers/projects/boards/issues_controller_spec.rb b/spec/controllers/projects/boards/issues_controller_spec.rb
index ad15e3942a5..3d0533cb516 100644
--- a/spec/controllers/projects/boards/issues_controller_spec.rb
+++ b/spec/controllers/projects/boards/issues_controller_spec.rb
@@ -90,7 +90,7 @@ describe Projects::Boards::IssuesController do
params = {
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
board_id: board.to_param,
list_id: list.try(:to_param)
}
@@ -146,7 +146,7 @@ describe Projects::Boards::IssuesController do
sign_in(user)
post :create, namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
board_id: board.to_param,
list_id: list.to_param,
issue: { title: title },
@@ -209,7 +209,7 @@ describe Projects::Boards::IssuesController do
sign_in(user)
patch :update, namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
board_id: board.to_param,
id: issue.to_param,
from_list_id: from_list_id,
diff --git a/spec/controllers/projects/boards/lists_controller_spec.rb b/spec/controllers/projects/boards/lists_controller_spec.rb
index b3f9f76a50c..432f3c53c90 100644
--- a/spec/controllers/projects/boards/lists_controller_spec.rb
+++ b/spec/controllers/projects/boards/lists_controller_spec.rb
@@ -47,7 +47,7 @@ describe Projects::Boards::ListsController do
sign_in(user)
get :index, namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
board_id: board.to_param,
format: :json
end
@@ -104,7 +104,7 @@ describe Projects::Boards::ListsController do
sign_in(user)
post :create, namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
board_id: board.to_param,
list: { label_id: label_id },
format: :json
@@ -157,7 +157,7 @@ describe Projects::Boards::ListsController do
sign_in(user)
patch :update, namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
board_id: board.to_param,
id: list.to_param,
list: { position: position },
@@ -200,7 +200,7 @@ describe Projects::Boards::ListsController do
sign_in(user)
delete :destroy, namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
board_id: board.to_param,
id: list.to_param,
format: :json
@@ -244,7 +244,7 @@ describe Projects::Boards::ListsController do
sign_in(user)
post :generate, namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
board_id: board.to_param,
format: :json
end
diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb
index cc19035740e..aed3a45c413 100644
--- a/spec/controllers/projects/boards_controller_spec.rb
+++ b/spec/controllers/projects/boards_controller_spec.rb
@@ -50,8 +50,8 @@ describe Projects::BoardsController do
end
def list_boards(format: :html)
- get :index, namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ get :index, namespace_id: project.namespace,
+ project_id: project,
format: format
end
end
@@ -100,8 +100,8 @@ describe Projects::BoardsController do
end
def read_board(board:, format: :html)
- get :show, namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ get :show, namespace_id: project.namespace,
+ project_id: project,
id: board.to_param,
format: format
end
diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb
index 9de03876755..e70737376af 100644
--- a/spec/controllers/projects/branches_controller_spec.rb
+++ b/spec/controllers/projects/branches_controller_spec.rb
@@ -22,8 +22,8 @@ describe Projects::BranchesController do
sign_in(user)
post :create,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
branch_name: branch,
ref: ref
end
@@ -68,7 +68,7 @@ describe Projects::BranchesController do
describe "created from the new branch button on issues" do
let(:branch) { "1-feature-branch" }
- let!(:issue) { create(:issue, project: project) }
+ let(:issue) { create(:issue, project: project) }
before do
sign_in(user)
@@ -76,8 +76,8 @@ describe Projects::BranchesController do
it 'redirects' do
post :create,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
branch_name: branch,
issue_iid: issue.iid
@@ -89,12 +89,49 @@ describe Projects::BranchesController do
expect(SystemNoteService).to receive(:new_issue_branch).with(issue, project, user, "1-feature-branch")
post :create,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
branch_name: branch,
issue_iid: issue.iid
end
+ context 'repository-less project' do
+ let(:project) { create :empty_project }
+
+ it 'redirects to newly created branch' do
+ result = { status: :success, branch: double(name: branch) }
+
+ expect_any_instance_of(CreateBranchService).to receive(:execute).and_return(result)
+ expect(SystemNoteService).to receive(:new_issue_branch).and_return(true)
+
+ post :create,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ branch_name: branch,
+ issue_iid: issue.iid
+
+ expect(response).to redirect_to namespace_project_tree_path(project.namespace, project, branch)
+ end
+
+ it 'redirects to autodeploy setup page' do
+ result = { status: :success, branch: double(name: branch) }
+
+ project.services << build(:kubernetes_service)
+
+ expect_any_instance_of(CreateBranchService).to receive(:execute).and_return(result)
+ expect(SystemNoteService).to receive(:new_issue_branch).and_return(true)
+
+ post :create,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ branch_name: branch,
+ issue_iid: issue.iid
+
+ expect(response.location).to include(namespace_project_new_blob_path(project.namespace, project, branch))
+ expect(response).to have_http_status(302)
+ end
+ end
+
context 'without issue feature access' do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
@@ -106,8 +143,8 @@ describe Projects::BranchesController do
expect(SystemNoteService).not_to receive(:new_issue_branch)
post :create,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
branch_name: branch,
issue_iid: issue.iid
end
@@ -126,8 +163,8 @@ describe Projects::BranchesController do
post :destroy,
format: :html,
id: 'foo/bar/baz',
- namespace_id: project.namespace.to_param,
- project_id: project.to_param
+ namespace_id: project.namespace,
+ project_id: project
expect(response).to have_http_status(303)
end
@@ -142,8 +179,8 @@ describe Projects::BranchesController do
post :destroy,
format: :js,
id: branch,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param
+ namespace_id: project.namespace,
+ project_id: project
end
context "valid branch name, valid source" do
@@ -173,8 +210,8 @@ describe Projects::BranchesController do
describe "DELETE destroy_all_merged" do
def destroy_all_merged
delete :destroy_all_merged,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param
+ namespace_id: project.namespace,
+ project_id: project
end
context 'when user is allowed to push' do
diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb
index ebd2d0e092b..640baa3a01c 100644
--- a/spec/controllers/projects/commit_controller_spec.rb
+++ b/spec/controllers/projects/commit_controller_spec.rb
@@ -17,8 +17,8 @@ describe Projects::CommitController do
def go(extra_params = {})
params = {
- namespace_id: project.namespace.to_param,
- project_id: project.to_param
+ namespace_id: project.namespace,
+ project_id: project
}
get :show, params.merge(extra_params)
@@ -125,8 +125,8 @@ describe Projects::CommitController do
it 'renders it' do
get(:show,
- namespace_id: fork_project.namespace.to_param,
- project_id: fork_project.to_param,
+ namespace_id: fork_project.namespace,
+ project_id: fork_project,
id: commit.id)
expect(response).to be_success
@@ -139,8 +139,8 @@ describe Projects::CommitController do
commit = project.commit('5937ac0a7beb003549fc5fd26fc247adbce4a52e')
get(:branches,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
id: commit.id)
expect(assigns(:branches)).to include("master", "feature_conflict")
@@ -152,8 +152,8 @@ describe Projects::CommitController do
context 'when target branch is not provided' do
it 'renders the 404 page' do
post(:revert,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
id: commit.id)
expect(response).not_to be_success
@@ -164,8 +164,8 @@ describe Projects::CommitController do
context 'when the revert was successful' do
it 'redirects to the commits page' do
post(:revert,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
target_branch: 'master',
id: commit.id)
@@ -177,8 +177,8 @@ describe Projects::CommitController do
context 'when the revert failed' do
before do
post(:revert,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
target_branch: 'master',
id: commit.id)
end
@@ -186,8 +186,8 @@ describe Projects::CommitController do
it 'redirects to the commit page' do
# Reverting a commit that has been already reverted.
post(:revert,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
target_branch: 'master',
id: commit.id)
@@ -201,8 +201,8 @@ describe Projects::CommitController do
context 'when target branch is not provided' do
it 'renders the 404 page' do
post(:cherry_pick,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
id: master_pickable_commit.id)
expect(response).not_to be_success
@@ -213,8 +213,8 @@ describe Projects::CommitController do
context 'when the cherry-pick was successful' do
it 'redirects to the commits page' do
post(:cherry_pick,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
target_branch: 'master',
id: master_pickable_commit.id)
@@ -226,8 +226,8 @@ describe Projects::CommitController do
context 'when the cherry_pick failed' do
before do
post(:cherry_pick,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
target_branch: 'master',
id: master_pickable_commit.id)
end
@@ -235,8 +235,8 @@ describe Projects::CommitController do
it 'redirects to the commit page' do
# Cherry-picking a commit that has been already cherry-picked.
post(:cherry_pick,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
target_branch: 'master',
id: master_pickable_commit.id)
@@ -249,8 +249,8 @@ describe Projects::CommitController do
describe 'GET diff_for_path' do
def diff_for_path(extra_params = {})
params = {
- namespace_id: project.namespace.to_param,
- project_id: project.to_param
+ namespace_id: project.namespace,
+ project_id: project
}
get :diff_for_path, params.merge(extra_params)
@@ -313,8 +313,8 @@ describe Projects::CommitController do
describe 'GET pipelines' do
def get_pipelines(extra_params = {})
params = {
- namespace_id: project.namespace.to_param,
- project_id: project.to_param
+ namespace_id: project.namespace,
+ project_id: project
}
get :pipelines, params.merge(extra_params)
diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb
index 54b8d1108a5..e26731fb691 100644
--- a/spec/controllers/projects/commits_controller_spec.rb
+++ b/spec/controllers/projects/commits_controller_spec.rb
@@ -16,8 +16,8 @@ describe Projects::CommitsController do
context "when the ref does not exist with the suffix" do
it "renders as atom" do
get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
id: "master.atom")
expect(response).to be_success
@@ -33,8 +33,8 @@ describe Projects::CommitsController do
allow_any_instance_of(Repository).to receive(:commit).with('master.atom').and_return(commit)
get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
id: "master.atom")
end
diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb
index e811c76fb31..15ac4e0925a 100644
--- a/spec/controllers/projects/compare_controller_spec.rb
+++ b/spec/controllers/projects/compare_controller_spec.rb
@@ -13,8 +13,8 @@ describe Projects::CompareController do
it 'compare shows some diffs' do
get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
from: ref_from,
to: ref_to)
@@ -25,8 +25,8 @@ describe Projects::CompareController do
it 'compare shows some diffs with ignore whitespace change option' do
get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
from: '08f22f25',
to: '66eceea0',
w: 1)
@@ -43,8 +43,8 @@ describe Projects::CompareController do
describe 'non-existent refs' do
it 'uses invalid source ref' do
get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
from: 'non-existent',
to: ref_to)
@@ -55,8 +55,8 @@ describe Projects::CompareController do
it 'uses invalid target ref' do
get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
from: ref_from,
to: 'non-existent')
@@ -67,8 +67,8 @@ describe Projects::CompareController do
it 'redirects back to index when params[:from] is empty and preserves params[:to]' do
post(:create,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
from: '',
to: 'master')
@@ -77,8 +77,8 @@ describe Projects::CompareController do
it 'redirects back to index when params[:to] is empty and preserves params[:from]' do
post(:create,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
from: 'master',
to: '')
@@ -87,8 +87,8 @@ describe Projects::CompareController do
it 'redirects back to index when params[:from] and params[:to] are empty' do
post(:create,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
from: '',
to: '')
@@ -99,8 +99,8 @@ describe Projects::CompareController do
describe 'GET diff_for_path' do
def diff_for_path(extra_params = {})
params = {
- namespace_id: project.namespace.to_param,
- project_id: project.to_param
+ namespace_id: project.namespace,
+ project_id: project
}
get :diff_for_path, params.merge(extra_params)
diff --git a/spec/controllers/projects/cycle_analytics_controller_spec.rb b/spec/controllers/projects/cycle_analytics_controller_spec.rb
index 6a6d71a16ee..6fae52edbad 100644
--- a/spec/controllers/projects/cycle_analytics_controller_spec.rb
+++ b/spec/controllers/projects/cycle_analytics_controller_spec.rb
@@ -13,8 +13,8 @@ describe Projects::CycleAnalyticsController do
context 'with no data' do
it 'is true' do
get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param)
+ namespace_id: project.namespace,
+ project_id: project)
expect(response).to be_success
expect(assigns(:cycle_analytics_no_data)).to eq(true)
@@ -32,8 +32,8 @@ describe Projects::CycleAnalyticsController do
it 'is false' do
get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param)
+ namespace_id: project.namespace,
+ project_id: project)
expect(response).to be_success
expect(assigns(:cycle_analytics_no_data)).to eq(false)
diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
index 7ac1d62d1b1..84d119f1867 100644
--- a/spec/controllers/projects/environments_controller_spec.rb
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -3,9 +3,12 @@ require 'spec_helper'
describe Projects::EnvironmentsController do
include ApiHelpers
- let(:environment) { create(:environment) }
- let(:project) { environment.project }
- let(:user) { create(:user) }
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project) }
+
+ let(:environment) do
+ create(:environment, name: 'production', project: project)
+ end
before do
project.team << [user, :master]
@@ -22,14 +25,58 @@ describe Projects::EnvironmentsController do
end
end
- context 'when requesting JSON response' do
- it 'responds with correct JSON' do
- get :index, environment_params(format: :json)
+ context 'when requesting JSON response for folders' do
+ before do
+ create(:environment, project: project,
+ name: 'staging/review-1',
+ state: :available)
+
+ create(:environment, project: project,
+ name: 'staging/review-2',
+ state: :available)
+
+ create(:environment, project: project,
+ name: 'staging/review-3',
+ state: :stopped)
+ end
+
+ let(:environments) { json_response['environments'] }
+
+ context 'when requesting available environments scope' do
+ before do
+ get :index, environment_params(format: :json, scope: :available)
+ end
+
+ it 'responds with a payload describing available environments' do
+ expect(environments.count).to eq 2
+ expect(environments.first['name']).to eq 'production'
+ expect(environments.second['name']).to eq 'staging'
+ expect(environments.second['size']).to eq 2
+ expect(environments.second['latest']['name']).to eq 'staging/review-2'
+ end
- first_environment = json_response.first
+ it 'contains values describing environment scopes sizes' do
+ expect(json_response['available_count']).to eq 3
+ expect(json_response['stopped_count']).to eq 1
+ end
+ end
- expect(first_environment).not_to be_empty
- expect(first_environment['name']). to eq environment.name
+ context 'when requesting stopped environments scope' do
+ before do
+ get :index, environment_params(format: :json, scope: :stopped)
+ end
+
+ it 'responds with a payload describing stopped environments' do
+ expect(environments.count).to eq 1
+ expect(environments.first['name']).to eq 'staging'
+ expect(environments.first['size']).to eq 1
+ expect(environments.first['latest']['name']).to eq 'staging/review-3'
+ end
+
+ it 'contains values describing environment scopes sizes' do
+ expect(json_response['available_count']).to eq 3
+ expect(json_response['stopped_count']).to eq 1
+ end
end
end
end
diff --git a/spec/controllers/projects/find_file_controller_spec.rb b/spec/controllers/projects/find_file_controller_spec.rb
index a4884256c92..6a5433bcc9c 100644
--- a/spec/controllers/projects/find_file_controller_spec.rb
+++ b/spec/controllers/projects/find_file_controller_spec.rb
@@ -17,8 +17,8 @@ describe Projects::FindFileController do
before do
get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
id: id)
end
@@ -36,8 +36,8 @@ describe Projects::FindFileController do
describe "GET #list" do
def go(format: 'json')
get :list,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
id: id,
format: format
end
diff --git a/spec/controllers/projects/forks_controller_spec.rb b/spec/controllers/projects/forks_controller_spec.rb
index a867668d97b..8282d79298f 100644
--- a/spec/controllers/projects/forks_controller_spec.rb
+++ b/spec/controllers/projects/forks_controller_spec.rb
@@ -9,8 +9,8 @@ describe Projects::ForksController do
describe 'GET index' do
def get_forks
get :index,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param
+ namespace_id: project.namespace,
+ project_id: project
end
context 'when fork is public' do
@@ -71,8 +71,8 @@ describe Projects::ForksController do
describe 'GET new' do
def get_new
get :new,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param
+ namespace_id: project.namespace,
+ project_id: project
end
context 'when user is signed in' do
@@ -99,8 +99,8 @@ describe Projects::ForksController do
describe 'POST create' do
def post_create
post :create,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
namespace_key: user.namespace.id
end
diff --git a/spec/controllers/projects/graphs_controller_spec.rb b/spec/controllers/projects/graphs_controller_spec.rb
index bbe8e4bf6b2..c4a7aa7d63e 100644
--- a/spec/controllers/projects/graphs_controller_spec.rb
+++ b/spec/controllers/projects/graphs_controller_spec.rb
@@ -34,7 +34,7 @@ describe Projects::GraphsController do
end
it 'sets the correct colour according to language' do
- get(:languages, namespace_id: project.namespace.path, project_id: project.path, id: 'master')
+ get(:languages, namespace_id: project.namespace, project_id: project, id: 'master')
expected_values.each do |val|
expect(assigns(:languages)).to include(a_hash_including(val))
diff --git a/spec/controllers/projects/group_links_controller_spec.rb b/spec/controllers/projects/group_links_controller_spec.rb
index a976a9c27ab..ca4a8e871c0 100644
--- a/spec/controllers/projects/group_links_controller_spec.rb
+++ b/spec/controllers/projects/group_links_controller_spec.rb
@@ -14,8 +14,8 @@ describe Projects::GroupLinksController do
describe '#create' do
shared_context 'link project to group' do
before do
- post(:create, namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ post(:create, namespace_id: project.namespace,
+ project_id: project,
link_group_id: group.id,
link_group_access: ProjectGroupLink.default_access)
end
@@ -50,8 +50,8 @@ describe Projects::GroupLinksController do
context 'when project group id equal link group id' do
before do
- post(:create, namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ post(:create, namespace_id: project.namespace,
+ project_id: project,
link_group_id: group2.id,
link_group_access: ProjectGroupLink.default_access)
end
@@ -69,8 +69,8 @@ describe Projects::GroupLinksController do
context 'when link group id is not present' do
before do
- post(:create, namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ post(:create, namespace_id: project.namespace,
+ project_id: project,
link_group_access: ProjectGroupLink.default_access)
end
diff --git a/spec/controllers/projects/imports_controller_spec.rb b/spec/controllers/projects/imports_controller_spec.rb
index 2acbba469e3..7c75815f3c4 100644
--- a/spec/controllers/projects/imports_controller_spec.rb
+++ b/spec/controllers/projects/imports_controller_spec.rb
@@ -13,13 +13,13 @@ describe Projects::ImportsController do
end
it 'renders template' do
- get :show, namespace_id: project.namespace.to_param, project_id: project.to_param
+ get :show, namespace_id: project.namespace.to_param, project_id: project
expect(response).to render_template :show
end
it 'sets flash.now if params is present' do
- get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, continue: { to: '/', notice_now: 'Started' }
+ get :show, namespace_id: project.namespace.to_param, project_id: project, continue: { to: '/', notice_now: 'Started' }
expect(flash.now[:notice]).to eq 'Started'
end
@@ -39,13 +39,13 @@ describe Projects::ImportsController do
end
it 'renders template' do
- get :show, namespace_id: project.namespace.to_param, project_id: project.to_param
+ get :show, namespace_id: project.namespace.to_param, project_id: project
expect(response).to render_template :show
end
it 'sets flash.now if params is present' do
- get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, continue: { to: '/', notice_now: 'In progress' }
+ get :show, namespace_id: project.namespace.to_param, project_id: project, continue: { to: '/', notice_now: 'In progress' }
expect(flash.now[:notice]).to eq 'In progress'
end
@@ -57,7 +57,7 @@ describe Projects::ImportsController do
end
it 'redirects to new_namespace_project_import_path' do
- get :show, namespace_id: project.namespace.to_param, project_id: project.to_param
+ get :show, namespace_id: project.namespace.to_param, project_id: project
expect(response).to redirect_to new_namespace_project_import_path(project.namespace, project)
end
@@ -72,7 +72,7 @@ describe Projects::ImportsController do
it 'redirects to namespace_project_path' do
allow_any_instance_of(Project).to receive(:forked?).and_return(true)
- get :show, namespace_id: project.namespace.to_param, project_id: project.to_param
+ get :show, namespace_id: project.namespace.to_param, project_id: project
expect(flash[:notice]).to eq 'The project was successfully forked.'
expect(response).to redirect_to namespace_project_path(project.namespace, project)
@@ -81,7 +81,7 @@ describe Projects::ImportsController do
context 'when project is external' do
it 'redirects to namespace_project_path' do
- get :show, namespace_id: project.namespace.to_param, project_id: project.to_param
+ get :show, namespace_id: project.namespace.to_param, project_id: project
expect(flash[:notice]).to eq 'The project was successfully imported.'
expect(response).to redirect_to namespace_project_path(project.namespace, project)
@@ -97,7 +97,7 @@ describe Projects::ImportsController do
end
it 'redirects to params[:to]' do
- get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, continue: params
+ get :show, namespace_id: project.namespace.to_param, project_id: project, continue: params
expect(flash[:notice]).to eq params[:notice]
expect(response).to redirect_to params[:to]
@@ -111,7 +111,7 @@ describe Projects::ImportsController do
end
it 'redirects to namespace_project_path' do
- get :show, namespace_id: project.namespace.to_param, project_id: project.to_param
+ get :show, namespace_id: project.namespace.to_param, project_id: project
expect(response).to redirect_to namespace_project_path(project.namespace, project)
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 4b89381eb96..e493b9396f6 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -12,7 +12,7 @@ describe Projects::IssuesController do
allow(project).to receive(:external_issue_tracker).and_return(external)
controller.instance_variable_set(:@project, project)
- get :index, namespace_id: project.namespace.path, project_id: project
+ get :index, namespace_id: project.namespace, project_id: project
expect(response).to redirect_to('https://example.com/project')
end
@@ -24,14 +24,16 @@ describe Projects::IssuesController do
project.team << [user, :developer]
end
+ it_behaves_like "issuables list meta-data", :issue
+
it "returns index" do
- get :index, namespace_id: project.namespace.path, project_id: project.path
+ get :index, namespace_id: project.namespace, project_id: project
expect(response).to have_http_status(200)
end
it "returns 301 if request path doesn't match project path" do
- get :index, namespace_id: project.namespace.path, project_id: project.path.upcase
+ get :index, namespace_id: project.namespace, project_id: project.path.upcase
expect(response).to redirect_to(namespace_project_issues_path(project.namespace, project))
end
@@ -40,7 +42,7 @@ describe Projects::IssuesController do
project.issues_enabled = false
project.save
- get :index, namespace_id: project.namespace.path, project_id: project.path
+ get :index, namespace_id: project.namespace, project_id: project
expect(response).to have_http_status(404)
end
@@ -48,7 +50,7 @@ describe Projects::IssuesController do
controller.instance_variable_set(:@project, project)
allow(project).to receive(:default_issues_tracker?).and_return(false)
- get :index, namespace_id: project.namespace.path, project_id: project.path
+ get :index, namespace_id: project.namespace, project_id: project
expect(response).to have_http_status(404)
end
end
@@ -65,8 +67,8 @@ describe Projects::IssuesController do
it 'redirects to last_page if page number is larger than number of pages' do
get :index,
- namespace_id: project.namespace.path.to_param,
- project_id: project.path.to_param,
+ namespace_id: project.namespace.to_param,
+ project_id: project,
page: (last_page + 1).to_param
expect(response).to redirect_to(namespace_project_issues_path(page: last_page, state: controller.params[:state], scope: controller.params[:scope]))
@@ -74,8 +76,8 @@ describe Projects::IssuesController do
it 'redirects to specified page' do
get :index,
- namespace_id: project.namespace.path.to_param,
- project_id: project.path.to_param,
+ namespace_id: project.namespace.to_param,
+ project_id: project,
page: last_page.to_param
expect(assigns(:issues).current_page).to eq(last_page)
@@ -92,7 +94,7 @@ describe Projects::IssuesController do
end
it 'builds a new issue' do
- get :new, namespace_id: project.namespace.path, project_id: project
+ get :new, namespace_id: project.namespace, project_id: project
expect(assigns(:issue)).to be_a_new(Issue)
end
@@ -102,7 +104,7 @@ describe Projects::IssuesController do
project_with_repository.team << [user, :developer]
mr = create(:merge_request_with_diff_notes, source_project: project_with_repository)
- get :new, namespace_id: project_with_repository.namespace.path, project_id: project_with_repository, merge_request_for_resolving_discussions: mr.iid
+ get :new, namespace_id: project_with_repository.namespace, project_id: project_with_repository, merge_request_for_resolving_discussions: mr.iid
expect(assigns(:issue).title).not_to be_empty
expect(assigns(:issue).description).not_to be_empty
@@ -115,7 +117,7 @@ describe Projects::IssuesController do
allow(project).to receive(:external_issue_tracker).and_return(external)
controller.instance_variable_set(:@project, project)
- get :new, namespace_id: project.namespace.path, project_id: project
+ get :new, namespace_id: project.namespace, project_id: project
expect(response).to redirect_to('https://example.com/issues/new')
end
@@ -150,10 +152,117 @@ describe Projects::IssuesController do
end
end
+ context 'Akismet is enabled' do
+ let(:project) { create(:project_empty_repo, :public) }
+
+ before do
+ stub_application_setting(recaptcha_enabled: true)
+ allow_any_instance_of(SpamService).to receive(:check_for_spam?).and_return(true)
+ end
+
+ context 'when an issue is not identified as spam' do
+ before do
+ allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false)
+ allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(false)
+ end
+
+ it 'normally updates the issue' do
+ expect { update_issue(title: 'Foo') }.to change { issue.reload.title }.to('Foo')
+ end
+ end
+
+ context 'when an issue is identified as spam' do
+ before { allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true) }
+
+ context 'when captcha is not verified' do
+ def update_spam_issue
+ update_issue(title: 'Spam Title', description: 'Spam lives here')
+ end
+
+ before { allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false) }
+
+ it 'rejects an issue recognized as a spam' do
+ expect { update_spam_issue }.not_to change{ issue.reload.title }
+ end
+
+ it 'rejects an issue recognized as a spam when recaptcha disabled' do
+ stub_application_setting(recaptcha_enabled: false)
+
+ expect { update_spam_issue }.not_to change{ issue.reload.title }
+ end
+
+ it 'creates a spam log' do
+ update_spam_issue
+
+ spam_logs = SpamLog.all
+
+ expect(spam_logs.count).to eq(1)
+ expect(spam_logs.first.title).to eq('Spam Title')
+ expect(spam_logs.first.recaptcha_verified).to be_falsey
+ end
+
+ it 'renders verify template' do
+ update_spam_issue
+
+ expect(response).to render_template(:verify)
+ end
+ end
+
+ context 'when captcha is verified' do
+ let(:spammy_title) { 'Whatever' }
+ let!(:spam_logs) { create_list(:spam_log, 2, user: user, title: spammy_title) }
+
+ def update_verified_issue
+ update_issue({ title: spammy_title },
+ { spam_log_id: spam_logs.last.id,
+ recaptcha_verification: true })
+ end
+
+ before do
+ allow_any_instance_of(described_class).to receive(:verify_recaptcha)
+ .and_return(true)
+ end
+
+ it 'redirect to issue page' do
+ update_verified_issue
+
+ expect(response).
+ to redirect_to(namespace_project_issue_path(project.namespace, project, issue))
+ end
+
+ it 'accepts an issue after recaptcha is verified' do
+ expect{ update_verified_issue }.to change{ issue.reload.title }.to(spammy_title)
+ end
+
+ it 'marks spam log as recaptcha_verified' do
+ expect { update_verified_issue }.to change { SpamLog.last.recaptcha_verified }.from(false).to(true)
+ end
+
+ it 'does not mark spam log as recaptcha_verified when it does not belong to current_user' do
+ spam_log = create(:spam_log)
+
+ expect { update_issue(spam_log_id: spam_log.id, recaptcha_verification: true) }.
+ not_to change { SpamLog.last.recaptcha_verified }
+ end
+ end
+ end
+ end
+
+ def update_issue(issue_params = {}, additional_params = {})
+ params = {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: issue.iid,
+ issue: issue_params
+ }.merge(additional_params)
+
+ put :update, params
+ end
+
def move_issue
put :update,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: issue.iid,
issue: { title: 'New title' },
move_to_project_id: another_project.id
@@ -233,7 +342,7 @@ describe Projects::IssuesController do
def get_issues
get :index,
namespace_id: project.namespace.to_param,
- project_id: project.to_param
+ project_id: project
end
end
@@ -296,7 +405,7 @@ describe Projects::IssuesController do
def go(id:)
get :show,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: id
end
end
@@ -307,7 +416,7 @@ describe Projects::IssuesController do
def go(id:)
get :edit,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: id
end
end
@@ -318,7 +427,7 @@ describe Projects::IssuesController do
def go(id:)
put :update,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: id,
issue: { title: 'New title' }
end
@@ -333,7 +442,7 @@ describe Projects::IssuesController do
post :create, {
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
issue: { title: 'Title', description: 'Description' }.merge(issue_attrs)
}.merge(additional_params)
@@ -355,7 +464,7 @@ describe Projects::IssuesController do
end
def post_issue(issue_params)
- post :create, namespace_id: project.namespace.to_param, project_id: project.to_param, issue: issue_params, merge_request_for_resolving_discussions: merge_request.iid
+ post :create, namespace_id: project.namespace.to_param, project_id: project, issue: issue_params, merge_request_for_resolving_discussions: merge_request.iid
end
it 'creates an issue for the project' do
@@ -382,7 +491,7 @@ describe Projects::IssuesController do
allow_any_instance_of(SpamService).to receive(:check_for_spam?).and_return(true)
end
- context 'when an issue is not identified as a spam' do
+ context 'when an issue is not identified as spam' do
before do
allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false)
allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(false)
@@ -393,7 +502,7 @@ describe Projects::IssuesController do
end
end
- context 'when an issue is identified as a spam' do
+ context 'when an issue is identified as spam' do
before { allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true) }
context 'when captcha is not verified' do
@@ -498,8 +607,8 @@ describe Projects::IssuesController do
project.team << [admin, :master]
sign_in(admin)
post :mark_as_spam, {
- namespace_id: project.namespace.path,
- project_id: project.path,
+ namespace_id: project.namespace,
+ project_id: project,
id: issue.iid
}
end
@@ -515,7 +624,7 @@ describe Projects::IssuesController do
context "when the user is a developer" do
before { sign_in(user) }
it "rejects a developer to destroy an issue" do
- delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: issue.iid
+ delete :destroy, namespace_id: project.namespace, project_id: project, id: issue.iid
expect(response).to have_http_status(404)
end
end
@@ -528,7 +637,7 @@ describe Projects::IssuesController do
before { sign_in(owner) }
it "deletes the issue" do
- delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: issue.iid
+ delete :destroy, namespace_id: project.namespace, project_id: project, id: issue.iid
expect(response).to have_http_status(302)
expect(controller).to set_flash[:notice].to(/The issue was successfully deleted\./).now
@@ -537,7 +646,7 @@ describe Projects::IssuesController do
it 'delegates the update of the todos count cache to TodoService' do
expect_any_instance_of(TodoService).to receive(:destroy_issue).with(issue, owner).once
- delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: issue.iid
+ delete :destroy, namespace_id: project.namespace, project_id: project, id: issue.iid
end
end
end
@@ -550,8 +659,8 @@ describe Projects::IssuesController do
it "toggles the award emoji" do
expect do
- post(:toggle_award_emoji, namespace_id: project.namespace.path,
- project_id: project.path, id: issue.iid, name: "thumbsup")
+ post(:toggle_award_emoji, namespace_id: project.namespace,
+ project_id: project, id: issue.iid, name: "thumbsup")
end.to change { issue.award_emoji.count }.by(1)
expect(response).to have_http_status(200)
diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb
index 3e0326dd47d..6a6e9bf378a 100644
--- a/spec/controllers/projects/labels_controller_spec.rb
+++ b/spec/controllers/projects/labels_controller_spec.rb
@@ -67,7 +67,7 @@ describe Projects::LabelsController do
end
def list_labels
- get :index, namespace_id: project.namespace.to_param, project_id: project.to_param
+ get :index, namespace_id: project.namespace.to_param, project_id: project
end
end
@@ -76,7 +76,7 @@ describe Projects::LabelsController do
let(:personal_project) { create(:empty_project, namespace: user.namespace) }
it 'creates labels' do
- post :generate, namespace_id: personal_project.namespace.to_param, project_id: personal_project.to_param
+ post :generate, namespace_id: personal_project.namespace.to_param, project_id: personal_project
expect(response).to have_http_status(302)
end
@@ -84,7 +84,7 @@ describe Projects::LabelsController do
context 'project belonging to a group' do
it 'creates labels' do
- post :generate, namespace_id: project.namespace.to_param, project_id: project.to_param
+ post :generate, namespace_id: project.namespace.to_param, project_id: project
expect(response).to have_http_status(302)
end
@@ -109,7 +109,7 @@ describe Projects::LabelsController do
end
def toggle_subscription(label)
- post :toggle_subscription, namespace_id: project.namespace.to_param, project_id: project.to_param, id: label.to_param
+ post :toggle_subscription, namespace_id: project.namespace.to_param, project_id: project, id: label.to_param
end
end
@@ -119,7 +119,7 @@ describe Projects::LabelsController do
context 'not group owner' do
it 'denies access' do
- post :promote, namespace_id: project.namespace.to_param, project_id: project.to_param, id: label_1.to_param
+ post :promote, namespace_id: project.namespace.to_param, project_id: project, id: label_1.to_param
expect(response).to have_http_status(404)
end
@@ -131,13 +131,13 @@ describe Projects::LabelsController do
end
it 'gives access' do
- post :promote, namespace_id: project.namespace.to_param, project_id: project.to_param, id: label_1.to_param
+ post :promote, namespace_id: project.namespace.to_param, project_id: project, id: label_1.to_param
expect(response).to redirect_to(namespace_project_labels_path)
end
it 'promotes the label' do
- post :promote, namespace_id: project.namespace.to_param, project_id: project.to_param, id: label_1.to_param
+ post :promote, namespace_id: project.namespace.to_param, project_id: project, id: label_1.to_param
expect(Label.where(id: label_1.id)).to be_empty
expect(GroupLabel.find_by(title: promoted_label_name)).not_to be_nil
@@ -151,7 +151,7 @@ describe Projects::LabelsController do
end
it 'returns to label list' do
- post :promote, namespace_id: project.namespace.to_param, project_id: project.to_param, id: label_1.to_param
+ post :promote, namespace_id: project.namespace.to_param, project_id: project, id: label_1.to_param
expect(response).to redirect_to(namespace_project_labels_path)
end
end
diff --git a/spec/controllers/projects/mattermosts_controller_spec.rb b/spec/controllers/projects/mattermosts_controller_spec.rb
index cae733f0cfb..c5abf11cfa5 100644
--- a/spec/controllers/projects/mattermosts_controller_spec.rb
+++ b/spec/controllers/projects/mattermosts_controller_spec.rb
@@ -18,7 +18,7 @@ describe Projects::MattermostsController do
it 'accepts the request' do
get(:new,
namespace_id: project.namespace.to_param,
- project_id: project.to_param)
+ project_id: project)
expect(response).to have_http_status(200)
end
@@ -30,7 +30,7 @@ describe Projects::MattermostsController do
subject do
post(:create,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
mattermost: mattermost_params)
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 63780802cfa..d9cb429132f 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -51,10 +51,11 @@ describe Projects::MergeRequestsController do
def submit_new_merge_request(format: :html)
get :new,
namespace_id: fork_project.namespace.to_param,
- project_id: fork_project.to_param,
+ project_id: fork_project,
merge_request: {
source_branch: 'remove-submodule',
- target_branch: 'master' },
+ target_branch: 'master'
+ },
format: format
end
end
@@ -63,7 +64,7 @@ describe Projects::MergeRequestsController do
it "loads labels into the @labels variable" do
get action,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: merge_request.iid,
format: 'html'
expect(assigns(:labels)).not_to be_nil
@@ -75,7 +76,7 @@ describe Projects::MergeRequestsController do
it "does generally work" do
get(:show,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: merge_request.iid,
format: format)
@@ -89,7 +90,7 @@ describe Projects::MergeRequestsController do
get(:show,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: merge_request.iid,
format: format)
end
@@ -97,7 +98,7 @@ describe Projects::MergeRequestsController do
it "renders it" do
get(:show,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: merge_request.iid,
format: format)
@@ -110,7 +111,7 @@ describe Projects::MergeRequestsController do
get(:show,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: merge_request.iid,
format: format)
@@ -125,7 +126,7 @@ describe Projects::MergeRequestsController do
it "triggers workhorse to serve the request" do
get(:show,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: merge_request.iid,
format: :diff)
@@ -137,7 +138,7 @@ describe Projects::MergeRequestsController do
it 'triggers workhorse to serve the request' do
get(:show,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: merge_request.iid,
format: :patch)
@@ -147,13 +148,17 @@ describe Projects::MergeRequestsController do
end
describe 'GET index' do
+ let!(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
+
def get_merge_requests(page = nil)
get :index,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
state: 'opened', page: page.to_param
end
+ it_behaves_like "issuables list meta-data", :merge_request
+
context 'when page param' do
let(:last_page) { project.merge_requests.page().total_pages }
let!(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
@@ -211,8 +216,8 @@ describe Projects::MergeRequestsController do
it 'closes MR without errors' do
post :update,
- namespace_id: project.namespace.path,
- project_id: project.path,
+ namespace_id: project.namespace,
+ project_id: project,
id: merge_request.iid,
merge_request: {
state_event: 'close'
@@ -226,8 +231,8 @@ describe Projects::MergeRequestsController do
merge_request.close!
put :update,
- namespace_id: project.namespace.path,
- project_id: project.path,
+ namespace_id: project.namespace,
+ project_id: project,
id: merge_request.iid,
merge_request: {
title: 'New title'
@@ -241,8 +246,8 @@ describe Projects::MergeRequestsController do
merge_request.close!
put :update,
- namespace_id: project.namespace.path,
- project_id: project.path,
+ namespace_id: project.namespace,
+ project_id: project,
id: merge_request.iid,
merge_request: {
target_branch: 'new_branch'
@@ -256,8 +261,8 @@ describe Projects::MergeRequestsController do
describe 'POST merge' do
let(:base_params) do
{
- namespace_id: project.namespace.path,
- project_id: project.path,
+ namespace_id: project.namespace,
+ project_id: project,
id: merge_request.iid,
format: 'raw'
}
@@ -421,7 +426,7 @@ describe Projects::MergeRequestsController do
describe "DELETE destroy" do
it "denies access to users unless they're admin or project owner" do
- delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: merge_request.iid
+ delete :destroy, namespace_id: project.namespace, project_id: project, id: merge_request.iid
expect(response).to have_http_status(404)
end
@@ -434,7 +439,7 @@ describe Projects::MergeRequestsController do
before { sign_in owner }
it "deletes the merge request" do
- delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: merge_request.iid
+ delete :destroy, namespace_id: project.namespace, project_id: project, id: merge_request.iid
expect(response).to have_http_status(302)
expect(controller).to set_flash[:notice].to(/The merge request was successfully deleted\./).now
@@ -443,7 +448,7 @@ describe Projects::MergeRequestsController do
it 'delegates the update of the todos count cache to TodoService' do
expect_any_instance_of(TodoService).to receive(:destroy_merge_request).with(merge_request, owner).once
- delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: merge_request.iid
+ delete :destroy, namespace_id: project.namespace, project_id: project, id: merge_request.iid
end
end
end
@@ -452,7 +457,7 @@ describe Projects::MergeRequestsController do
def go(extra_params = {})
params = {
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: merge_request.iid
}
@@ -532,7 +537,7 @@ describe Projects::MergeRequestsController do
def diff_for_path(extra_params = {})
params = {
namespace_id: project.namespace.to_param,
- project_id: project.to_param
+ project_id: project
}
get :diff_for_path, params.merge(extra_params)
@@ -596,7 +601,7 @@ describe Projects::MergeRequestsController do
before do
other_project.team << [user, :master]
- diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path, project_id: other_project.to_param)
+ diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path, project_id: other_project)
end
it 'returns a 404' do
@@ -662,7 +667,7 @@ describe Projects::MergeRequestsController do
def go(format: 'html')
get :commits,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: merge_request.iid,
format: format
end
@@ -702,7 +707,7 @@ describe Projects::MergeRequestsController do
before do
get :pipelines,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: merge_request.iid,
format: :json
end
@@ -721,7 +726,7 @@ describe Projects::MergeRequestsController do
get :conflicts,
namespace_id: merge_request_with_conflicts.project.namespace.to_param,
- project_id: merge_request_with_conflicts.project.to_param,
+ project_id: merge_request_with_conflicts.project,
id: merge_request_with_conflicts.iid,
format: 'json'
end
@@ -739,7 +744,7 @@ describe Projects::MergeRequestsController do
before do
get :conflicts,
namespace_id: merge_request_with_conflicts.project.namespace.to_param,
- project_id: merge_request_with_conflicts.project.to_param,
+ project_id: merge_request_with_conflicts.project,
id: merge_request_with_conflicts.iid,
format: 'json'
end
@@ -768,7 +773,7 @@ describe Projects::MergeRequestsController do
section['lines'].each do |line|
if section['conflict']
- expect(line['type']).to be_in(['old', 'new'])
+ expect(line['type']).to be_in(%w(old new))
expect(line.values_at('old_line', 'new_line')).to contain_exactly(nil, a_kind_of(Integer))
else
if line['type'].nil?
@@ -802,7 +807,7 @@ describe Projects::MergeRequestsController do
post :remove_wip,
namespace_id: merge_request.project.namespace.to_param,
- project_id: merge_request.project.to_param,
+ project_id: merge_request.project,
id: merge_request.iid
expect(merge_request.reload.title).to eq(merge_request.wipless_title)
@@ -813,7 +818,7 @@ describe Projects::MergeRequestsController do
def conflict_for_path(path)
get :conflict_for_path,
namespace_id: merge_request_with_conflicts.project.namespace.to_param,
- project_id: merge_request_with_conflicts.project.to_param,
+ project_id: merge_request_with_conflicts.project,
id: merge_request_with_conflicts.iid,
old_path: path,
new_path: path,
@@ -869,7 +874,7 @@ describe Projects::MergeRequestsController do
def resolve_conflicts(files)
post :resolve_conflicts,
namespace_id: merge_request_with_conflicts.project.namespace.to_param,
- project_id: merge_request_with_conflicts.project.to_param,
+ project_id: merge_request_with_conflicts.project,
id: merge_request_with_conflicts.iid,
format: 'json',
files: files,
@@ -1020,7 +1025,7 @@ describe Projects::MergeRequestsController do
post :assign_related_issues,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: merge_request.iid
end
@@ -1075,7 +1080,7 @@ describe Projects::MergeRequestsController do
get :ci_environments_status,
namespace_id: merge_request.project.namespace.to_param,
- project_id: merge_request.project.to_param,
+ project_id: merge_request.project,
id: merge_request.iid, format: 'json'
end
@@ -1088,8 +1093,8 @@ describe Projects::MergeRequestsController do
describe 'GET merge_widget_refresh' do
let(:params) do
{
- namespace_id: project.namespace.path,
- project_id: project.path,
+ namespace_id: project.namespace,
+ project_id: project,
id: merge_request.iid,
format: :raw
}
@@ -1139,15 +1144,15 @@ describe Projects::MergeRequestsController do
end
end
- context 'when no special status for MR' do
+ context 'when MR does not have special state' do
let(:merge_request) { create(:merge_request, source_project: project) }
it 'returns an OK response' do
expect(response).to have_http_status(:ok)
end
- it 'sets status to nil' do
- expect(assigns(:status)).to be_nil
+ it 'sets status to success' do
+ expect(assigns(:status)).to eq(:success)
expect(response).to render_template('merge')
end
end
diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb
index 1ed2ee3ab4a..04bb5cbbd59 100644
--- a/spec/controllers/projects/pipelines_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_controller_spec.rb
@@ -12,10 +12,13 @@ describe Projects::PipelinesController do
describe 'GET index.json' do
before do
- create_list(:ci_empty_pipeline, 2, project: project)
+ create(:ci_empty_pipeline, status: 'pending', project: project)
+ create(:ci_empty_pipeline, status: 'running', project: project)
+ create(:ci_empty_pipeline, status: 'created', project: project)
+ create(:ci_empty_pipeline, status: 'success', project: project)
- get :index, namespace_id: project.namespace.path,
- project_id: project.path,
+ get :index, namespace_id: project.namespace,
+ project_id: project,
format: :json
end
@@ -23,9 +26,11 @@ describe Projects::PipelinesController do
expect(response).to have_http_status(:ok)
expect(json_response).to include('pipelines')
- expect(json_response['pipelines'].count).to eq 2
- expect(json_response['count']['all']).to eq 2
- expect(json_response['count']['running_or_pending']).to eq 2
+ expect(json_response['pipelines'].count).to eq 4
+ expect(json_response['count']['all']).to eq 4
+ expect(json_response['count']['running']).to eq 1
+ expect(json_response['count']['pending']).to eq 1
+ expect(json_response['count']['finished']).to eq 1
end
end
@@ -57,8 +62,8 @@ describe Projects::PipelinesController do
end
def get_stage(name)
- get :stage, namespace_id: project.namespace.path,
- project_id: project.path,
+ get :stage, namespace_id: project.namespace,
+ project_id: project,
id: pipeline.id,
stage: name,
format: :json
diff --git a/spec/controllers/projects/protected_branches_controller_spec.rb b/spec/controllers/projects/protected_branches_controller_spec.rb
index da6112a13f7..e378b5714fe 100644
--- a/spec/controllers/projects/protected_branches_controller_spec.rb
+++ b/spec/controllers/projects/protected_branches_controller_spec.rb
@@ -4,7 +4,7 @@ describe Projects::ProtectedBranchesController do
describe "GET #index" do
let(:project) { create(:project_empty_repo, :public) }
it "redirects empty repo to projects page" do
- get(:index, namespace_id: project.namespace.to_param, project_id: project.to_param)
+ get(:index, namespace_id: project.namespace.to_param, project_id: project)
end
end
end
diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb
index b23d6e257ba..4cebe3884bf 100644
--- a/spec/controllers/projects/raw_controller_spec.rb
+++ b/spec/controllers/projects/raw_controller_spec.rb
@@ -10,7 +10,7 @@ describe Projects::RawController do
it 'delivers ASCII file' do
get(:show,
namespace_id: public_project.namespace.to_param,
- project_id: public_project.to_param,
+ project_id: public_project,
id: id)
expect(response).to have_http_status(200)
@@ -27,7 +27,7 @@ describe Projects::RawController do
it 'sets image content type header' do
get(:show,
namespace_id: public_project.namespace.to_param,
- project_id: public_project.to_param,
+ project_id: public_project,
id: id)
expect(response).to have_http_status(200)
@@ -51,7 +51,7 @@ describe Projects::RawController do
expect(controller).to receive(:send_file).with("#{Gitlab.config.shared.path}/lfs-objects/91/ef/f75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897", filename: "lfs_object.iso", disposition: 'attachment')
get(:show,
namespace_id: public_project.namespace.to_param,
- project_id: public_project.to_param,
+ project_id: public_project,
id: id)
expect(response).to have_http_status(200)
@@ -62,7 +62,7 @@ describe Projects::RawController do
it 'does not serve the file' do
get(:show,
namespace_id: public_project.namespace.to_param,
- project_id: public_project.to_param,
+ project_id: public_project,
id: id)
expect(response).to have_http_status(404)
diff --git a/spec/controllers/projects/refs_controller_spec.rb b/spec/controllers/projects/refs_controller_spec.rb
index d8fb4667c67..3a3e7467ef2 100644
--- a/spec/controllers/projects/refs_controller_spec.rb
+++ b/spec/controllers/projects/refs_controller_spec.rb
@@ -13,7 +13,7 @@ describe Projects::RefsController do
def default_get(format = :html)
get :logs_tree,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: 'master',
path: 'foo/bar/baz.html',
format: format
@@ -23,7 +23,7 @@ describe Projects::RefsController do
xhr :get,
:logs_tree,
namespace_id: project.namespace.to_param,
- project_id: project.to_param, id: 'master',
+ project_id: project, id: 'master',
path: 'foo/bar/baz.html', format: format
end
diff --git a/spec/controllers/projects/releases_controller_spec.rb b/spec/controllers/projects/releases_controller_spec.rb
index 69fcc26c77e..358f26dfb02 100644
--- a/spec/controllers/projects/releases_controller_spec.rb
+++ b/spec/controllers/projects/releases_controller_spec.rb
@@ -16,7 +16,7 @@ describe Projects::ReleasesController do
tag_id = release.tag
project.releases.destroy_all
- get :edit, namespace_id: project.namespace.path, project_id: project.path, tag_id: tag_id
+ get :edit, namespace_id: project.namespace, project_id: project, tag_id: tag_id
release = assigns(:release)
expect(release).not_to be_nil
@@ -24,7 +24,7 @@ describe Projects::ReleasesController do
end
it 'retrieves an existing release' do
- get :edit, namespace_id: project.namespace.path, project_id: project.path, tag_id: release.tag
+ get :edit, namespace_id: project.namespace, project_id: project, tag_id: release.tag
release = assigns(:release)
expect(release).not_to be_nil
@@ -48,7 +48,7 @@ describe Projects::ReleasesController do
def update_release(description)
put :update,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
tag_id: release.tag,
release: { description: description }
end
diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb
index 04e88879fb8..9c55d159fa0 100644
--- a/spec/controllers/projects/repositories_controller_spec.rb
+++ b/spec/controllers/projects/repositories_controller_spec.rb
@@ -6,7 +6,7 @@ describe Projects::RepositoriesController do
describe "GET archive" do
context 'as a guest' do
it 'responds with redirect in correct format' do
- get :archive, namespace_id: project.namespace.path, project_id: project.path, format: "zip"
+ get :archive, namespace_id: project.namespace, project_id: project, format: "zip"
expect(response.header["Content-Type"]).to start_with('text/html')
expect(response).to be_redirect
@@ -22,7 +22,7 @@ describe Projects::RepositoriesController do
end
it "uses Gitlab::Workhorse" do
- get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip"
+ get :archive, namespace_id: project.namespace, project_id: project, ref: "master", format: "zip"
expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:")
end
@@ -33,7 +33,7 @@ describe Projects::RepositoriesController do
end
it "renders Not Found" do
- get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip"
+ get :archive, namespace_id: project.namespace, project_id: project, ref: "master", format: "zip"
expect(response).to have_http_status(404)
end
diff --git a/spec/controllers/projects/runners_controller_spec.rb b/spec/controllers/projects/runners_controller_spec.rb
new file mode 100644
index 00000000000..0fa249e4405
--- /dev/null
+++ b/spec/controllers/projects/runners_controller_spec.rb
@@ -0,0 +1,75 @@
+require 'spec_helper'
+
+describe Projects::RunnersController do
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project) }
+ let(:runner) { create(:ci_runner) }
+
+ let(:params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: runner
+ }
+ end
+
+ before do
+ sign_in(user)
+ project.add_master(user)
+ project.runners << runner
+ end
+
+ describe '#update' do
+ it 'updates the runner and ticks the queue' do
+ new_desc = runner.description.swapcase
+
+ expect do
+ post :update, params.merge(runner: { description: new_desc } )
+ end.to change { runner.ensure_runner_queue_value }
+
+ runner.reload
+
+ expect(response).to have_http_status(302)
+ expect(runner.description).to eq(new_desc)
+ end
+ end
+
+ describe '#destroy' do
+ it 'destroys the runner' do
+ delete :destroy, params
+
+ expect(response).to have_http_status(302)
+ expect(Ci::Runner.find_by(id: runner.id)).to be_nil
+ end
+ end
+
+ describe '#resume' do
+ it 'marks the runner as active and ticks the queue' do
+ runner.update(active: false)
+
+ expect do
+ post :resume, params
+ end.to change { runner.ensure_runner_queue_value }
+
+ runner.reload
+
+ expect(response).to have_http_status(302)
+ expect(runner.active).to eq(true)
+ end
+ end
+
+ describe '#pause' do
+ it 'marks the runner as inactive and ticks the queue' do
+ runner.update(active: true)
+
+ expect do
+ post :pause, params
+ end.to change { runner.ensure_runner_queue_value }
+
+ runner.reload
+
+ expect(response).to have_http_status(302)
+ expect(runner.active).to eq(false)
+ end
+ end
+end
diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb
index 19e948d8fb8..24a59caff4e 100644
--- a/spec/controllers/projects/snippets_controller_spec.rb
+++ b/spec/controllers/projects/snippets_controller_spec.rb
@@ -17,16 +17,16 @@ describe Projects::SnippetsController do
it 'redirects to last_page if page number is larger than number of pages' do
get :index,
- namespace_id: project.namespace.path,
- project_id: project.path, page: (last_page + 1).to_param
+ namespace_id: project.namespace,
+ project_id: project, page: (last_page + 1).to_param
expect(response).to redirect_to(namespace_project_snippets_path(page: last_page))
end
it 'redirects to specified page' do
get :index,
- namespace_id: project.namespace.path,
- project_id: project.path, page: last_page.to_param
+ namespace_id: project.namespace,
+ project_id: project, page: last_page.to_param
expect(assigns(:snippets).current_page).to eq(last_page)
expect(response).to have_http_status(200)
@@ -38,7 +38,7 @@ describe Projects::SnippetsController do
context 'when anonymous' do
it 'does not include the private snippet' do
- get :index, namespace_id: project.namespace.path, project_id: project.path
+ get :index, namespace_id: project.namespace, project_id: project
expect(assigns(:snippets)).not_to include(project_snippet)
expect(response).to have_http_status(200)
@@ -49,7 +49,7 @@ describe Projects::SnippetsController do
before { sign_in(user) }
it 'renders the snippet' do
- get :index, namespace_id: project.namespace.path, project_id: project.path
+ get :index, namespace_id: project.namespace, project_id: project
expect(assigns(:snippets)).to include(project_snippet)
expect(response).to have_http_status(200)
@@ -60,7 +60,7 @@ describe Projects::SnippetsController do
before { sign_in(user2) }
it 'renders the snippet' do
- get :index, namespace_id: project.namespace.path, project_id: project.path
+ get :index, namespace_id: project.namespace, project_id: project
expect(assigns(:snippets)).to include(project_snippet)
expect(response).to have_http_status(200)
@@ -70,16 +70,16 @@ describe Projects::SnippetsController do
end
describe 'POST #create' do
- def create_snippet(project, snippet_params = {})
+ def create_snippet(project, snippet_params = {}, additional_params = {})
sign_in(user)
project.add_developer(user)
post :create, {
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
project_snippet: { title: 'Title', content: 'Content' }.merge(snippet_params)
- }
+ }.merge(additional_params)
end
context 'when the snippet is spam' do
@@ -87,35 +87,179 @@ describe Projects::SnippetsController do
allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
end
- context 'when the project is private' do
- let(:private_project) { create(:project_empty_repo, :private) }
+ context 'when the snippet is private' do
+ it 'creates the snippet' do
+ expect { create_snippet(project, visibility_level: Snippet::PRIVATE) }.
+ to change { Snippet.count }.by(1)
+ end
+ end
+
+ context 'when the snippet is public' do
+ it 'rejects the shippet' do
+ expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
+ not_to change { Snippet.count }
+ expect(response).to render_template(:new)
+ end
+
+ it 'creates a spam log' do
+ expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
+ to change { SpamLog.count }.by(1)
+ end
- context 'when the snippet is public' do
- it 'creates the snippet' do
- expect { create_snippet(private_project, visibility_level: Snippet::PUBLIC) }.
- to change { Snippet.count }.by(1)
+ it 'renders :new with recaptcha disabled' do
+ stub_application_setting(recaptcha_enabled: false)
+
+ create_snippet(project, visibility_level: Snippet::PUBLIC)
+
+ expect(response).to render_template(:new)
+ end
+
+ context 'recaptcha enabled' do
+ before do
+ stub_application_setting(recaptcha_enabled: true)
+ end
+
+ it 'renders :verify with recaptcha enabled' do
+ create_snippet(project, visibility_level: Snippet::PUBLIC)
+
+ expect(response).to render_template(:verify)
+ end
+
+ it 'renders snippet page when recaptcha verified' do
+ spammy_title = 'Whatever'
+
+ spam_logs = create_list(:spam_log, 2, user: user, title: spammy_title)
+ create_snippet(project,
+ { visibility_level: Snippet::PUBLIC },
+ { spam_log_id: spam_logs.last.id,
+ recaptcha_verification: true })
+
+ expect(response).to redirect_to(Snippet.last)
end
end
end
+ end
+ end
+
+ describe 'PUT #update' do
+ let(:project) { create :project, :public }
+ let(:snippet) { create :project_snippet, author: user, project: project, visibility_level: visibility_level }
+
+ def update_snippet(snippet_params = {}, additional_params = {})
+ sign_in(user)
+
+ project.add_developer(user)
+
+ put :update, {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: snippet.id,
+ project_snippet: { title: 'Title', content: 'Content' }.merge(snippet_params)
+ }.merge(additional_params)
+
+ snippet.reload
+ end
+
+ context 'when the snippet is spam' do
+ before do
+ allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
+ end
+
+ context 'when the snippet is private' do
+ let(:visibility_level) { Snippet::PRIVATE }
+
+ it 'updates the snippet' do
+ expect { update_snippet(title: 'Foo') }.
+ to change { snippet.reload.title }.to('Foo')
+ end
+ end
+
+ context 'when the snippet is public' do
+ let(:visibility_level) { Snippet::PUBLIC }
+
+ it 'rejects the shippet' do
+ expect { update_snippet(title: 'Foo') }.
+ not_to change { snippet.reload.title }
+ end
- context 'when the project is public' do
- context 'when the snippet is private' do
- it 'creates the snippet' do
- expect { create_snippet(project, visibility_level: Snippet::PRIVATE) }.
- to change { Snippet.count }.by(1)
+ it 'creates a spam log' do
+ expect { update_snippet(title: 'Foo') }.
+ to change { SpamLog.count }.by(1)
+ end
+
+ it 'renders :edit with recaptcha disabled' do
+ stub_application_setting(recaptcha_enabled: false)
+
+ update_snippet(title: 'Foo')
+
+ expect(response).to render_template(:edit)
+ end
+
+ context 'recaptcha enabled' do
+ before do
+ stub_application_setting(recaptcha_enabled: true)
+ end
+
+ it 'renders :verify with recaptcha enabled' do
+ update_snippet(title: 'Foo')
+
+ expect(response).to render_template(:verify)
+ end
+
+ it 'renders snippet page when recaptcha verified' do
+ spammy_title = 'Whatever'
+
+ spam_logs = create_list(:spam_log, 2, user: user, title: spammy_title)
+ snippet = update_snippet({ title: spammy_title },
+ { spam_log_id: spam_logs.last.id,
+ recaptcha_verification: true })
+
+ expect(response).to redirect_to(snippet)
end
end
+ end
+
+ context 'when the private snippet is made public' do
+ let(:visibility_level) { Snippet::PRIVATE }
+
+ it 'rejects the shippet' do
+ expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }.
+ not_to change { snippet.reload.title }
+ end
+
+ it 'creates a spam log' do
+ expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }.
+ to change { SpamLog.count }.by(1)
+ end
+
+ it 'renders :edit with recaptcha disabled' do
+ stub_application_setting(recaptcha_enabled: false)
+
+ update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC)
+
+ expect(response).to render_template(:edit)
+ end
+
+ context 'recaptcha enabled' do
+ before do
+ stub_application_setting(recaptcha_enabled: true)
+ end
- context 'when the snippet is public' do
- it 'rejects the shippet' do
- expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
- not_to change { Snippet.count }
- expect(response).to render_template(:new)
+ it 'renders :verify with recaptcha enabled' do
+ update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC)
+
+ expect(response).to render_template(:verify)
end
- it 'creates a spam log' do
- expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
- to change { SpamLog.count }.by(1)
+ it 'renders snippet page when recaptcha verified' do
+ spammy_title = 'Whatever'
+
+ spam_logs = create_list(:spam_log, 2, user: user, title: spammy_title)
+ snippet = update_snippet({ title: spammy_title, visibility_level: Snippet::PUBLIC },
+ { spam_log_id: spam_logs.last.id,
+ recaptcha_verification: true })
+
+ expect(response).to redirect_to(snippet)
end
end
end
@@ -137,8 +281,8 @@ describe Projects::SnippetsController do
sign_in(admin)
post :mark_as_spam,
- namespace_id: project.namespace.path,
- project_id: project.path,
+ namespace_id: project.namespace,
+ project_id: project,
id: snippet.id
end
@@ -156,7 +300,7 @@ describe Projects::SnippetsController do
context 'when anonymous' do
it 'responds with status 404' do
- get action, namespace_id: project.namespace.path, project_id: project.path, id: project_snippet.to_param
+ get action, namespace_id: project.namespace, project_id: project, id: project_snippet.to_param
expect(response).to have_http_status(404)
end
@@ -166,7 +310,7 @@ describe Projects::SnippetsController do
before { sign_in(user) }
it 'renders the snippet' do
- get action, namespace_id: project.namespace.path, project_id: project.path, id: project_snippet.to_param
+ get action, namespace_id: project.namespace, project_id: project, id: project_snippet.to_param
expect(assigns(:snippet)).to eq(project_snippet)
expect(response).to have_http_status(200)
@@ -177,7 +321,7 @@ describe Projects::SnippetsController do
before { sign_in(user2) }
it 'renders the snippet' do
- get action, namespace_id: project.namespace.path, project_id: project.path, id: project_snippet.to_param
+ get action, namespace_id: project.namespace, project_id: project, id: project_snippet.to_param
expect(assigns(:snippet)).to eq(project_snippet)
expect(response).to have_http_status(200)
@@ -188,7 +332,7 @@ describe Projects::SnippetsController do
context 'when the project snippet does not exist' do
context 'when anonymous' do
it 'responds with status 404' do
- get action, namespace_id: project.namespace.path, project_id: project.path, id: 42
+ get action, namespace_id: project.namespace, project_id: project, id: 42
expect(response).to have_http_status(404)
end
@@ -198,7 +342,7 @@ describe Projects::SnippetsController do
before { sign_in(user) }
it 'responds with status 404' do
- get action, namespace_id: project.namespace.path, project_id: project.path, id: 42
+ get action, namespace_id: project.namespace, project_id: project, id: 42
expect(response).to have_http_status(404)
end
@@ -206,4 +350,37 @@ describe Projects::SnippetsController do
end
end
end
+
+ describe 'GET #raw' do
+ let(:project_snippet) do
+ create(
+ :project_snippet, :public,
+ project: project,
+ author: user,
+ content: "first line\r\nsecond line\r\nthird line"
+ )
+ end
+
+ context 'CRLF line ending' do
+ let(:params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: project_snippet.to_param
+ }
+ end
+
+ it 'returns LF line endings by default' do
+ get :raw, params
+
+ expect(response.body).to eq("first line\nsecond line\nthird line")
+ end
+
+ it 'does not convert line endings when parameter present' do
+ get :raw, params.merge(line_ending: :raw)
+
+ expect(response.body).to eq("first line\r\nsecond line\r\nthird line")
+ end
+ end
+ end
end
diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb
index c36a5fdd66c..fc97bac64cd 100644
--- a/spec/controllers/projects/tags_controller_spec.rb
+++ b/spec/controllers/projects/tags_controller_spec.rb
@@ -6,7 +6,7 @@ describe Projects::TagsController do
let!(:invalid_release) { create(:release, project: project, tag: 'does-not-exist') }
describe 'GET index' do
- before { get :index, namespace_id: project.namespace.to_param, project_id: project.to_param }
+ before { get :index, namespace_id: project.namespace.to_param, project_id: project }
it 'returns the tags for the page' do
expect(assigns(:tags).map(&:name)).to eq(['v1.1.0', 'v1.0.0'])
@@ -19,7 +19,7 @@ describe Projects::TagsController do
end
describe 'GET show' do
- before { get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, id: id }
+ before { get :show, namespace_id: project.namespace.to_param, project_id: project, id: id }
context "valid tag" do
let(:id) { 'v1.0.0' }
diff --git a/spec/controllers/projects/templates_controller_spec.rb b/spec/controllers/projects/templates_controller_spec.rb
index 80f84a388ce..066a1831852 100644
--- a/spec/controllers/projects/templates_controller_spec.rb
+++ b/spec/controllers/projects/templates_controller_spec.rb
@@ -20,7 +20,7 @@ describe Projects::TemplatesController do
describe '#show' do
it 'renders template name and content as json' do
- get(:show, namespace_id: project.namespace.to_param, template_type: "issue", key: "bug", project_id: project.path, format: :json)
+ get(:show, namespace_id: project.namespace.to_param, template_type: "issue", key: "bug", project_id: project, format: :json)
expect(response.status).to eq(200)
expect(body["name"]).to eq("bug")
@@ -29,21 +29,21 @@ describe Projects::TemplatesController do
it 'renders 404 when unauthorized' do
sign_in(user2)
- get(:show, namespace_id: project.namespace.to_param, template_type: "issue", key: "bug", project_id: project.path, format: :json)
+ get(:show, namespace_id: project.namespace.to_param, template_type: "issue", key: "bug", project_id: project, format: :json)
expect(response.status).to eq(404)
end
it 'renders 404 when template type is not found' do
sign_in(user)
- get(:show, namespace_id: project.namespace.to_param, template_type: "dont_exist", key: "bug", project_id: project.path, format: :json)
+ get(:show, namespace_id: project.namespace.to_param, template_type: "dont_exist", key: "bug", project_id: project, format: :json)
expect(response.status).to eq(404)
end
it 'renders 404 without errors' do
sign_in(user)
- expect { get(:show, namespace_id: project.namespace.to_param, template_type: "dont_exist", key: "bug", project_id: project.path, format: :json) }.not_to raise_error
+ expect { get(:show, namespace_id: project.namespace.to_param, template_type: "dont_exist", key: "bug", project_id: project, format: :json) }.not_to raise_error
end
end
end
diff --git a/spec/controllers/projects/todo_controller_spec.rb b/spec/controllers/projects/todo_controller_spec.rb
index 415c264e0dd..9a7beeff6fe 100644
--- a/spec/controllers/projects/todo_controller_spec.rb
+++ b/spec/controllers/projects/todo_controller_spec.rb
@@ -12,8 +12,8 @@ describe Projects::TodosController do
describe 'POST create' do
def go
post :create,
- namespace_id: project.namespace.path,
- project_id: project.path,
+ namespace_id: project.namespace,
+ project_id: project,
issuable_id: issue.id,
issuable_type: 'issue',
format: 'html'
@@ -80,8 +80,8 @@ describe Projects::TodosController do
describe 'POST create' do
def go
post :create,
- namespace_id: project.namespace.path,
- project_id: project.path,
+ namespace_id: project.namespace,
+ project_id: project,
issuable_id: merge_request.id,
issuable_type: 'merge_request',
format: 'html'
diff --git a/spec/controllers/projects/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb
index b81645a3d2d..ab94e292e48 100644
--- a/spec/controllers/projects/tree_controller_spec.rb
+++ b/spec/controllers/projects/tree_controller_spec.rb
@@ -18,7 +18,7 @@ describe Projects::TreeController do
before do
get(:show,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: id)
end
@@ -74,7 +74,7 @@ describe Projects::TreeController do
before do
get(:show,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: id)
end
@@ -94,7 +94,7 @@ describe Projects::TreeController do
before do
post(:create_dir,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: 'master',
dir_name: path,
target_branch: target_branch,
diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb
index f1c8891e87a..699c6f77cec 100644
--- a/spec/controllers/projects/uploads_controller_spec.rb
+++ b/spec/controllers/projects/uploads_controller_spec.rb
@@ -16,7 +16,7 @@ describe Projects::UploadsController do
it "returns an error" do
post :create,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
format: :json
expect(response).to have_http_status(422)
end
@@ -26,7 +26,7 @@ describe Projects::UploadsController do
before do
post :create,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
file: jpg,
format: :json
end
@@ -41,7 +41,7 @@ describe Projects::UploadsController do
before do
post :create,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
file: txt,
format: :json
end
@@ -57,7 +57,7 @@ describe Projects::UploadsController do
let(:go) do
get :show,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
secret: "123456",
filename: "image.jpg"
end
@@ -170,68 +170,24 @@ describe Projects::UploadsController do
project.team << [user, :master]
end
- context "when the user is blocked" do
+ context "when the file exists" do
before do
- user.block
- project.team << [user, :master]
- end
-
- context "when the file exists" do
- before do
- allow_any_instance_of(FileUploader).to receive(:file).and_return(jpg)
- allow(jpg).to receive(:exists?).and_return(true)
- end
-
- context "when the file is an image" do
- before do
- allow_any_instance_of(FileUploader).to receive(:image?).and_return(true)
- end
-
- it "responds with status 200" do
- go
-
- expect(response).to have_http_status(200)
- end
- end
-
- context "when the file is not an image" do
- it "redirects to the sign in page" do
- go
-
- expect(response).to redirect_to(new_user_session_path)
- end
- end
+ allow_any_instance_of(FileUploader).to receive(:file).and_return(jpg)
+ allow(jpg).to receive(:exists?).and_return(true)
end
- context "when the file doesn't exist" do
- it "redirects to the sign in page" do
- go
+ it "responds with status 200" do
+ go
- expect(response).to redirect_to(new_user_session_path)
- end
+ expect(response).to have_http_status(200)
end
end
- context "when the user isn't blocked" do
- context "when the file exists" do
- before do
- allow_any_instance_of(FileUploader).to receive(:file).and_return(jpg)
- allow(jpg).to receive(:exists?).and_return(true)
- end
-
- it "responds with status 200" do
- go
-
- expect(response).to have_http_status(200)
- end
- end
-
- context "when the file doesn't exist" do
- it "responds with status 404" do
- go
+ context "when the file doesn't exist" do
+ it "responds with status 404" do
+ go
- expect(response).to have_http_status(404)
- end
+ expect(response).to have_http_status(404)
end
end
end
diff --git a/spec/controllers/projects/variables_controller_spec.rb b/spec/controllers/projects/variables_controller_spec.rb
index 9fa358f7d62..e3f3b4fe8eb 100644
--- a/spec/controllers/projects/variables_controller_spec.rb
+++ b/spec/controllers/projects/variables_controller_spec.rb
@@ -12,7 +12,7 @@ describe Projects::VariablesController do
describe 'POST #create' do
context 'variable is valid' do
it 'shows a success flash message' do
- post :create, namespace_id: project.namespace.to_param, project_id: project.to_param,
+ post :create, namespace_id: project.namespace.to_param, project_id: project,
variable: { key: "one", value: "two" }
expect(flash[:notice]).to include 'Variables were successfully updated.'
@@ -22,7 +22,7 @@ describe Projects::VariablesController do
context 'variable is invalid' do
it 'shows an alert flash message' do
- post :create, namespace_id: project.namespace.to_param, project_id: project.to_param,
+ post :create, namespace_id: project.namespace.to_param, project_id: project,
variable: { key: "..one", value: "two" }
expect(response).to render_template("projects/variables/show")
@@ -40,7 +40,7 @@ describe Projects::VariablesController do
end
it 'shows a success flash message' do
- post :update, namespace_id: project.namespace.to_param, project_id: project.to_param,
+ post :update, namespace_id: project.namespace.to_param, project_id: project,
id: variable.id, variable: { key: variable.key, value: 'two' }
expect(flash[:notice]).to include 'Variable was successfully updated.'
@@ -48,7 +48,7 @@ describe Projects::VariablesController do
end
it 'renders the action #show if the variable key is invalid' do
- post :update, namespace_id: project.namespace.to_param, project_id: project.to_param,
+ post :update, namespace_id: project.namespace.to_param, project_id: project,
id: variable.id, variable: { key: '?', value: variable.value }
expect(response).to have_http_status(200)
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index e7aa8745b99..202759664a0 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -35,7 +35,7 @@ describe ProjectsController do
let(:private_project) { create(:empty_project, :private) }
it "does not initialize notification setting" do
- get :show, namespace_id: private_project.namespace.path, id: private_project.path
+ get :show, namespace_id: private_project.namespace, id: private_project
expect(assigns(:notification_setting)).to be_nil
end
end
@@ -43,7 +43,7 @@ describe ProjectsController do
context "user has access to project" do
context "and does not have notification setting" do
it "initializes notification as disabled" do
- get :show, namespace_id: public_project.namespace.path, id: public_project.path
+ get :show, namespace_id: public_project.namespace, id: public_project
expect(assigns(:notification_setting).level).to eq("global")
end
end
@@ -56,7 +56,7 @@ describe ProjectsController do
end
it "shows current notification setting" do
- get :show, namespace_id: public_project.namespace.path, id: public_project.path
+ get :show, namespace_id: public_project.namespace, id: public_project
expect(assigns(:notification_setting).level).to eq("watch")
end
end
@@ -71,7 +71,7 @@ describe ProjectsController do
end
it 'shows wiki homepage' do
- get :show, namespace_id: project.namespace.path, id: project.path
+ get :show, namespace_id: project.namespace, id: project
expect(response).to render_template('projects/_wiki')
end
@@ -79,7 +79,7 @@ describe ProjectsController do
it 'shows issues list page if wiki is disabled' do
project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED)
- get :show, namespace_id: project.namespace.path, id: project.path
+ get :show, namespace_id: project.namespace, id: project
expect(response).to render_template('projects/issues/_issues')
end
@@ -88,7 +88,7 @@ describe ProjectsController do
project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED)
project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
- get :show, namespace_id: project.namespace.path, id: project.path
+ get :show, namespace_id: project.namespace, id: project
expect(response).to render_template("projects/_customize_workflow")
end
@@ -96,7 +96,7 @@ describe ProjectsController do
it 'shows activity if enabled by user' do
user.update_attribute(:project_view, 'activity')
- get :show, namespace_id: project.namespace.path, id: project.path
+ get :show, namespace_id: project.namespace, id: project
expect(response).to render_template("projects/_activity")
end
@@ -113,7 +113,7 @@ describe ProjectsController do
before do
user.update_attributes(project_view: project_view)
- get :show, namespace_id: empty_project.namespace.path, id: empty_project.path
+ get :show, namespace_id: empty_project.namespace, id: empty_project
end
it "renders the empty project view" do
@@ -133,7 +133,7 @@ describe ProjectsController do
before do
user.update_attributes(project_view: project_view)
- get :show, namespace_id: empty_project.namespace.path, id: empty_project.path
+ get :show, namespace_id: empty_project.namespace, id: empty_project
end
it "renders the empty project view" do
@@ -154,7 +154,7 @@ describe ProjectsController do
allow(controller).to receive(:current_user).and_return(user)
allow(user).to receive(:project_view).and_return('activity')
- get :show, namespace_id: public_project.namespace.path, id: public_project.path
+ get :show, namespace_id: public_project.namespace, id: public_project
expect(response).to render_template('_activity')
end
@@ -162,7 +162,7 @@ describe ProjectsController do
allow(controller).to receive(:current_user).and_return(user)
allow(user).to receive(:project_view).and_return('readme')
- get :show, namespace_id: public_project.namespace.path, id: public_project.path
+ get :show, namespace_id: public_project.namespace, id: public_project
expect(response).to render_template('_readme')
end
@@ -170,7 +170,7 @@ describe ProjectsController do
allow(controller).to receive(:current_user).and_return(user)
allow(user).to receive(:project_view).and_return('files')
- get :show, namespace_id: public_project.namespace.path, id: public_project.path
+ get :show, namespace_id: public_project.namespace, id: public_project
expect(response).to render_template('_files')
end
end
@@ -178,7 +178,7 @@ describe ProjectsController do
context "when requested with case sensitive namespace and project path" do
context "when there is a match with the same casing" do
it "loads the project" do
- get :show, namespace_id: public_project.namespace.path, id: public_project.path
+ get :show, namespace_id: public_project.namespace, id: public_project
expect(assigns(:project)).to eq(public_project)
expect(response).to have_http_status(200)
@@ -187,10 +187,10 @@ describe ProjectsController do
context "when there is a match with different casing" do
it "redirects to the normalized path" do
- get :show, namespace_id: public_project.namespace.path, id: public_project.path.upcase
+ get :show, namespace_id: public_project.namespace, id: public_project.path.upcase
expect(assigns(:project)).to eq(public_project)
- expect(response).to redirect_to("/#{public_project.path_with_namespace}")
+ expect(response).to redirect_to("/#{public_project.full_path}")
end
end
end
@@ -208,7 +208,7 @@ describe ProjectsController do
project = create(:empty_project, pending_delete: true)
sign_in(user)
- get :show, namespace_id: project.namespace.path, id: project.path
+ get :show, namespace_id: project.namespace, id: project
expect(response.status).to eq 404
end
@@ -218,7 +218,7 @@ describe ProjectsController do
it 'redirects to project page (format.html)' do
project = create(:project, :public)
- get :show, namespace_id: project.namespace.path, id: project.path, format: :git
+ get :show, namespace_id: project.namespace, id: project, format: :git
expect(response).to have_http_status(302)
expect(response).to redirect_to(namespace_project_path)
@@ -239,7 +239,7 @@ describe ProjectsController do
sign_in(admin)
put :update,
- namespace_id: project.namespace.to_param,
+ namespace_id: project.namespace,
id: project.id,
project: project_params
@@ -257,7 +257,7 @@ describe ProjectsController do
sign_in(admin)
orig_id = project.id
- delete :destroy, namespace_id: project.namespace.path, id: project.path
+ delete :destroy, namespace_id: project.namespace, id: project
expect { Project.find(orig_id) }.to raise_error(ActiveRecord::RecordNotFound)
expect(response).to have_http_status(302)
@@ -277,7 +277,7 @@ describe ProjectsController do
project.merge_requests << merge_request
sign_in(admin)
- delete :destroy, namespace_id: fork_project.namespace.path, id: fork_project.path
+ delete :destroy, namespace_id: fork_project.namespace, id: fork_project
expect(merge_request.reload.state).to eq('closed')
end
@@ -287,8 +287,8 @@ describe ProjectsController do
describe 'PUT #new_issue_address' do
subject do
put :new_issue_address,
- namespace_id: project.namespace.to_param,
- id: project.to_param
+ namespace_id: project.namespace,
+ id: project
user.reload
end
@@ -316,23 +316,23 @@ describe ProjectsController do
sign_in(user)
expect(user.starred?(public_project)).to be_falsey
post(:toggle_star,
- namespace_id: public_project.namespace.to_param,
- id: public_project.to_param)
+ namespace_id: public_project.namespace,
+ id: public_project)
expect(user.starred?(public_project)).to be_truthy
post(:toggle_star,
- namespace_id: public_project.namespace.to_param,
- id: public_project.to_param)
+ namespace_id: public_project.namespace,
+ id: public_project)
expect(user.starred?(public_project)).to be_falsey
end
it "does nothing if user is not signed in" do
post(:toggle_star,
- namespace_id: project.namespace.to_param,
- id: public_project.to_param)
+ namespace_id: project.namespace,
+ id: public_project)
expect(user.starred?(public_project)).to be_falsey
post(:toggle_star,
- namespace_id: project.namespace.to_param,
- id: public_project.to_param)
+ namespace_id: project.namespace,
+ id: public_project)
expect(user.starred?(public_project)).to be_falsey
end
end
@@ -366,8 +366,8 @@ describe ProjectsController do
it 'does nothing if project was not forked' do
delete(:remove_fork,
- namespace_id: unforked_project.namespace.to_param,
- id: unforked_project.to_param, format: :js)
+ namespace_id: unforked_project.namespace,
+ id: unforked_project, format: :js)
expect(flash[:notice]).to be_nil
expect(response).to render_template(:remove_fork)
@@ -377,8 +377,8 @@ describe ProjectsController do
it "does nothing if user is not signed in" do
delete(:remove_fork,
- namespace_id: project.namespace.to_param,
- id: project.to_param, format: :js)
+ namespace_id: project.namespace,
+ id: project, format: :js)
expect(response).to have_http_status(401)
end
end
@@ -387,7 +387,7 @@ describe ProjectsController do
let(:public_project) { create(:project, :public) }
it "gets a list of branches and tags" do
- get :refs, namespace_id: public_project.namespace.path, id: public_project.path
+ get :refs, namespace_id: public_project.namespace, id: public_project
parsed_body = JSON.parse(response.body)
expect(parsed_body["Branches"]).to include("master")
@@ -396,7 +396,7 @@ describe ProjectsController do
end
it "gets a list of branches, tags and commits" do
- get :refs, namespace_id: public_project.namespace.path, id: public_project.path, ref: "123456"
+ get :refs, namespace_id: public_project.namespace, id: public_project, ref: "123456"
parsed_body = JSON.parse(response.body)
expect(parsed_body["Branches"]).to include("master")
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
index dadcb90cfc2..5de3b9890ef 100644
--- a/spec/controllers/snippets_controller_spec.rb
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -139,12 +139,14 @@ describe SnippetsController do
end
describe 'POST #create' do
- def create_snippet(snippet_params = {})
+ def create_snippet(snippet_params = {}, additional_params = {})
sign_in(user)
post :create, {
personal_snippet: { title: 'Title', content: 'Content' }.merge(snippet_params)
- }
+ }.merge(additional_params)
+
+ Snippet.last
end
context 'when the snippet is spam' do
@@ -163,13 +165,164 @@ describe SnippetsController do
it 'rejects the shippet' do
expect { create_snippet(visibility_level: Snippet::PUBLIC) }.
not_to change { Snippet.count }
- expect(response).to render_template(:new)
end
it 'creates a spam log' do
expect { create_snippet(visibility_level: Snippet::PUBLIC) }.
to change { SpamLog.count }.by(1)
end
+
+ it 'renders :new with recaptcha disabled' do
+ stub_application_setting(recaptcha_enabled: false)
+
+ create_snippet(visibility_level: Snippet::PUBLIC)
+
+ expect(response).to render_template(:new)
+ end
+
+ context 'recaptcha enabled' do
+ before do
+ stub_application_setting(recaptcha_enabled: true)
+ end
+
+ it 'renders :verify with recaptcha enabled' do
+ create_snippet(visibility_level: Snippet::PUBLIC)
+
+ expect(response).to render_template(:verify)
+ end
+
+ it 'renders snippet page when recaptcha verified' do
+ spammy_title = 'Whatever'
+
+ spam_logs = create_list(:spam_log, 2, user: user, title: spammy_title)
+ snippet = create_snippet({ title: spammy_title },
+ { spam_log_id: spam_logs.last.id,
+ recaptcha_verification: true })
+
+ expect(response).to redirect_to(snippet_path(snippet))
+ end
+ end
+ end
+ end
+ end
+
+ describe 'PUT #update' do
+ let(:project) { create :project }
+ let(:snippet) { create :personal_snippet, author: user, project: project, visibility_level: visibility_level }
+
+ def update_snippet(snippet_params = {}, additional_params = {})
+ sign_in(user)
+
+ put :update, {
+ id: snippet.id,
+ personal_snippet: { title: 'Title', content: 'Content' }.merge(snippet_params)
+ }.merge(additional_params)
+
+ snippet.reload
+ end
+
+ context 'when the snippet is spam' do
+ before do
+ allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
+ end
+
+ context 'when the snippet is private' do
+ let(:visibility_level) { Snippet::PRIVATE }
+
+ it 'updates the snippet' do
+ expect { update_snippet(title: 'Foo') }.
+ to change { snippet.reload.title }.to('Foo')
+ end
+ end
+
+ context 'when a private snippet is made public' do
+ let(:visibility_level) { Snippet::PRIVATE }
+
+ it 'rejects the snippet' do
+ expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }.
+ not_to change { snippet.reload.title }
+ end
+
+ it 'creates a spam log' do
+ expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }.
+ to change { SpamLog.count }.by(1)
+ end
+
+ it 'renders :edit with recaptcha disabled' do
+ stub_application_setting(recaptcha_enabled: false)
+
+ update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC)
+
+ expect(response).to render_template(:edit)
+ end
+
+ context 'recaptcha enabled' do
+ before do
+ stub_application_setting(recaptcha_enabled: true)
+ end
+
+ it 'renders :verify with recaptcha enabled' do
+ update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC)
+
+ expect(response).to render_template(:verify)
+ end
+
+ it 'renders snippet page when recaptcha verified' do
+ spammy_title = 'Whatever'
+
+ spam_logs = create_list(:spam_log, 2, user: user, title: spammy_title)
+ snippet = update_snippet({ title: spammy_title, visibility_level: Snippet::PUBLIC },
+ { spam_log_id: spam_logs.last.id,
+ recaptcha_verification: true })
+
+ expect(response).to redirect_to(snippet)
+ end
+ end
+ end
+
+ context 'when the snippet is public' do
+ let(:visibility_level) { Snippet::PUBLIC }
+
+ it 'rejects the shippet' do
+ expect { update_snippet(title: 'Foo') }.
+ not_to change { snippet.reload.title }
+ end
+
+ it 'creates a spam log' do
+ expect { update_snippet(title: 'Foo') }.
+ to change { SpamLog.count }.by(1)
+ end
+
+ it 'renders :edit with recaptcha disabled' do
+ stub_application_setting(recaptcha_enabled: false)
+
+ update_snippet(title: 'Foo')
+
+ expect(response).to render_template(:edit)
+ end
+
+ context 'recaptcha enabled' do
+ before do
+ stub_application_setting(recaptcha_enabled: true)
+ end
+
+ it 'renders :verify with recaptcha enabled' do
+ update_snippet(title: 'Foo')
+
+ expect(response).to render_template(:verify)
+ end
+
+ it 'renders snippet page when recaptcha verified' do
+ spammy_title = 'Whatever'
+
+ spam_logs = create_list(:spam_log, 2, user: user, title: spammy_title)
+ snippet = update_snippet({ title: spammy_title },
+ { spam_log_id: spam_logs.last.id,
+ recaptcha_verification: true })
+
+ expect(response).to redirect_to(snippet_path(snippet))
+ end
+ end
end
end
end
@@ -286,6 +439,24 @@ describe SnippetsController do
expect(assigns(:snippet)).to eq(personal_snippet)
expect(response).to have_http_status(200)
end
+
+ context 'CRLF line ending' do
+ let(:personal_snippet) do
+ create(:personal_snippet, :public, author: user, content: "first line\r\nsecond line\r\nthird line")
+ end
+
+ it 'returns LF line endings by default' do
+ get action, id: personal_snippet.to_param
+
+ expect(response.body).to eq("first line\nsecond line\nthird line")
+ end
+
+ it 'does not convert line endings when parameter present' do
+ get action, id: personal_snippet.to_param, line_ending: :raw
+
+ expect(response.body).to eq("first line\r\nsecond line\r\nthird line")
+ end
+ end
end
context 'when not signed in' do
diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb
index 570d9fa43f8..c9584ddf18c 100644
--- a/spec/controllers/uploads_controller_spec.rb
+++ b/spec/controllers/uploads_controller_spec.rb
@@ -4,6 +4,28 @@ describe UploadsController do
let!(:user) { create(:user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
describe "GET show" do
+ context 'Content-Disposition security measures' do
+ let(:project) { create(:empty_project, :public) }
+
+ context 'for PNG files' do
+ it 'returns Content-Disposition: inline' do
+ note = create(:note, :with_attachment, project: project)
+ get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'image.png'
+
+ expect(response['Content-Disposition']).to start_with('inline;')
+ end
+ end
+
+ context 'for SVG files' do
+ it 'returns Content-Disposition: attachment' do
+ note = create(:note, :with_svg_attachment, project: project)
+ get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'image.svg'
+
+ expect(response['Content-Disposition']).to start_with('attachment;')
+ end
+ end
+ end
+
context "when viewing a user avatar" do
context "when signed in" do
before do
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 0397d5d4001..a90534d10ba 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -89,8 +89,9 @@ FactoryGirl.define do
tag true
end
- factory :ci_build_with_coverage do
+ trait :coverage do
coverage 99.9
+ coverage_regex '/(d+)/'
end
trait :trace do
@@ -99,6 +100,16 @@ FactoryGirl.define do
end
end
+ trait :erased do
+ erased_at Time.now
+ erased_by factory: :user
+ end
+
+ trait :queued do
+ queued_at Time.now
+ runner factory: :ci_runner
+ end
+
trait :artifacts do
after(:create) do |build, _|
build.artifacts_file =
@@ -128,5 +139,17 @@ FactoryGirl.define do
build.save!
end
end
+
+ trait :with_commit do
+ after(:build) do |build|
+ allow(build).to receive(:commit).and_return build(:commit, :without_author)
+ end
+ end
+
+ trait :with_commit_and_author do
+ after(:build) do |build|
+ allow(build).to receive(:commit).and_return build(:commit)
+ end
+ end
end
end
diff --git a/spec/factories/commits.rb b/spec/factories/commits.rb
index ac6eb0a7897..89e260cf65b 100644
--- a/spec/factories/commits.rb
+++ b/spec/factories/commits.rb
@@ -8,5 +8,15 @@ FactoryGirl.define do
initialize_with do
new(git_commit, project)
end
+
+ after(:build) do |commit|
+ allow(commit).to receive(:author).and_return build(:author)
+ end
+
+ trait :without_author do
+ after(:build) do |commit|
+ allow(commit).to receive(:author).and_return nil
+ end
+ end
end
end
diff --git a/spec/factories/keys.rb b/spec/factories/keys.rb
index d69c5b38d0a..dd93b439b2b 100644
--- a/spec/factories/keys.rb
+++ b/spec/factories/keys.rb
@@ -2,10 +2,13 @@ FactoryGirl.define do
factory :key do
title
key do
- "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0= dummy@gitlab.com"
+ 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0= dummy@gitlab.com'
end
factory :deploy_key, class: 'DeployKey' do
+ key do
+ 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFf6RYK3qu/RKF/3ndJmL5xgMLp3O96x8lTay+QGZ0+9FnnAXMdUqBq/ZU6d/gyMB4IaW3nHzM1w049++yAB6UPCzMB8Uo27K5/jyZCtj7Vm9PFNjF/8am1kp46c/SeYicQgQaSBdzIW3UDEa1Ef68qroOlvpi9PYZ/tA7M0YP0K5PXX+E36zaIRnJVMPT3f2k+GnrxtjafZrwFdpOP/Fol5BQLBgcsyiU+LM1SuaCrzd8c9vyaTA1CxrkxaZh+buAi0PmdDtaDrHd42gqZkXCKavyvgM5o2CkQ5LJHCgzpXy05qNFzmThBSkb+XtoxbyagBiGbVZtSVow6Xa7qewz'
+ end
end
factory :personal_key do
@@ -14,7 +17,7 @@ FactoryGirl.define do
factory :another_key do
key do
- "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDmTillFzNTrrGgwaCKaSj+QCz81E6jBc/s9av0+3b1Hwfxgkqjl4nAK/OD2NjgyrONDTDfR8cRN4eAAy6nY8GLkOyYBDyuc5nTMqs5z3yVuTwf3koGm/YQQCmo91psZ2BgDFTor8SVEE5Mm1D1k3JDMhDFxzzrOtRYFPci9lskTJaBjpqWZ4E9rDTD2q/QZntCqbC3wE9uSemRQB5f8kik7vD/AD8VQXuzKladrZKkzkONCPWsXDspUitjM8HkQdOf0PsYn1CMUC1xKYbCxkg5TkEosIwGv6CoEArUrdu/4+10LVslq494mAvEItywzrluCLCnwELfW+h/m8UHoVhZ"
+ 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDmTillFzNTrrGgwaCKaSj+QCz81E6jBc/s9av0+3b1Hwfxgkqjl4nAK/OD2NjgyrONDTDfR8cRN4eAAy6nY8GLkOyYBDyuc5nTMqs5z3yVuTwf3koGm/YQQCmo91psZ2BgDFTor8SVEE5Mm1D1k3JDMhDFxzzrOtRYFPci9lskTJaBjpqWZ4E9rDTD2q/QZntCqbC3wE9uSemRQB5f8kik7vD/AD8VQXuzKladrZKkzkONCPWsXDspUitjM8HkQdOf0PsYn1CMUC1xKYbCxkg5TkEosIwGv6CoEArUrdu/4+10LVslq494mAvEItywzrluCLCnwELfW+h/m8UHoVhZ'
end
factory :another_deploy_key, class: 'DeployKey' do
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index a21da7074f9..5c50cd7f4ad 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -97,7 +97,11 @@ FactoryGirl.define do
end
trait :with_attachment do
- attachment { fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "`/png") }
+ attachment { fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png") }
+ end
+
+ trait :with_svg_attachment do
+ attachment { fixture_file_upload(Rails.root + "spec/fixtures/unsanitized.svg", "image/svg+xml") }
end
end
end
diff --git a/spec/factories/services.rb b/spec/factories/services.rb
index a14a46c803e..51335bdcf1d 100644
--- a/spec/factories/services.rb
+++ b/spec/factories/services.rb
@@ -2,4 +2,14 @@ FactoryGirl.define do
factory :service do
project factory: :empty_project
end
+
+ factory :kubernetes_service do
+ project factory: :empty_project
+ active true
+ properties({
+ namespace: 'somepath',
+ api_url: 'https://kubernetes.example.com',
+ token: 'a' * 40,
+ })
+ end
end
diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb
index 91d6f39a5bf..a5265f1b189 100644
--- a/spec/factories/todos.rb
+++ b/spec/factories/todos.rb
@@ -14,6 +14,10 @@ FactoryGirl.define do
action { Todo::MENTIONED }
end
+ trait :directly_addressed do
+ action { Todo::DIRECTLY_ADDRESSED }
+ end
+
trait :on_commit do
commit_id RepoHelpers.sample_commit.id
target_type "Commit"
@@ -24,6 +28,10 @@ FactoryGirl.define do
target factory: :merge_request
end
+ trait :marked do
+ action { Todo::MARKED }
+ end
+
trait :approval_required do
action { Todo::APPROVAL_REQUIRED }
end
@@ -32,6 +40,10 @@ FactoryGirl.define do
action { Todo::UNMERGEABLE }
end
+ trait :pending do
+ state :pending
+ end
+
trait :done do
state :done
end
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
index c6f7869516e..249dabbaae8 100644
--- a/spec/factories/users.rb
+++ b/spec/factories/users.rb
@@ -14,10 +14,23 @@ FactoryGirl.define do
admin true
end
+ trait :blocked do
+ after(:build) { |user, _| user.block! }
+ end
+
+ trait :external do
+ external true
+ end
+
trait :two_factor do
two_factor_via_otp
end
+ trait :ghost do
+ ghost true
+ after(:build) { |user, _| user.block! }
+ end
+
trait :two_factor_via_otp do
before(:create) do |user|
user.otp_required_for_login = true
diff --git a/spec/factories/wiki_directories.rb b/spec/factories/wiki_directories.rb
new file mode 100644
index 00000000000..3f3c864ac2b
--- /dev/null
+++ b/spec/factories/wiki_directories.rb
@@ -0,0 +1,6 @@
+FactoryGirl.define do
+ factory :wiki_directory do
+ slug '/path_up_to/dir'
+ initialize_with { new(slug) }
+ end
+end
diff --git a/spec/factories/wiki_pages.rb b/spec/factories/wiki_pages.rb
index efa6cbe5bb1..4105f59e289 100644
--- a/spec/factories/wiki_pages.rb
+++ b/spec/factories/wiki_pages.rb
@@ -2,8 +2,26 @@ require 'ostruct'
FactoryGirl.define do
factory :wiki_page do
+ transient do
+ attrs do
+ {
+ title: 'Title',
+ content: 'Content for wiki page',
+ format: 'markdown'
+ }
+ end
+ end
+
page { OpenStruct.new(url_path: 'some-name') }
association :wiki, factory: :project_wiki, strategy: :build
initialize_with { new(wiki, page, true) }
+
+ before(:create) do |page, evaluator|
+ page.attributes = evaluator.attrs
+ end
+
+ to_create do |page|
+ page.create
+ end
end
end
diff --git a/spec/features/admin/admin_abuse_reports_spec.rb b/spec/features/admin/admin_abuse_reports_spec.rb
index 7fcfe5a54c7..340884fc986 100644
--- a/spec/features/admin/admin_abuse_reports_spec.rb
+++ b/spec/features/admin/admin_abuse_reports_spec.rb
@@ -30,5 +30,24 @@ describe "Admin::AbuseReports", feature: true, js: true do
end
end
end
+
+ describe 'if a many users have been reported for abuse' do
+ let(:report_count) { AbuseReport.default_per_page + 3 }
+
+ before do
+ report_count.times do
+ create(:abuse_report, user: create(:user))
+ end
+ end
+
+ describe 'in the abuse report view' do
+ it 'presents information about abuse report' do
+ visit admin_abuse_reports_path
+
+ expect(page).to have_selector('.pagination')
+ expect(page).to have_selector('.pagination .page', count: (report_count.to_f / AbuseReport.default_per_page).ceil)
+ end
+ end
+ end
end
end
diff --git a/spec/features/admin/admin_labels_spec.rb b/spec/features/admin/admin_labels_spec.rb
index eaa42aad0a7..6d6c9165c83 100644
--- a/spec/features/admin/admin_labels_spec.rb
+++ b/spec/features/admin/admin_labels_spec.rb
@@ -35,15 +35,16 @@ RSpec.describe 'admin issues labels' do
it 'deletes all labels', js: true do
page.within '.labels' do
page.all('.btn-remove').each do |remove|
- wait_for_ajax
remove.click
+ wait_for_ajax
end
end
- page.within '.manage-labels-list' do
- expect(page).not_to have_content('bug')
- expect(page).not_to have_content('feature_label')
- end
+ wait_for_ajax
+
+ expect(page).to have_content("There are no labels yet")
+ expect(page).not_to have_content('bug')
+ expect(page).not_to have_content('feature_label')
end
end
diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb
index f05fbe3d062..5dcc7d35d82 100644
--- a/spec/features/admin/admin_runners_spec.rb
+++ b/spec/features/admin/admin_runners_spec.rb
@@ -18,7 +18,7 @@ describe "Admin Runners" do
it 'has all necessary texts' do
expect(page).to have_text "To register a new Runner"
- expect(page).to have_text "Runners with last contact less than a minute ago: 1"
+ expect(page).to have_text "Runners with last contact more than a minute ago: 1"
end
describe 'search' do
diff --git a/spec/features/atom/dashboard_issues_spec.rb b/spec/features/atom/dashboard_issues_spec.rb
index 21ee6cedbae..a7c22615b89 100644
--- a/spec/features/atom/dashboard_issues_spec.rb
+++ b/spec/features/atom/dashboard_issues_spec.rb
@@ -23,7 +23,7 @@ describe "Dashboard Issues Feed", feature: true do
visit issues_dashboard_path(:atom, private_token: user.private_token, state: 'opened', assignee_id: user.id)
link = find('link[type="application/atom+xml"]')
- params = CGI::parse(URI.parse(link[:href]).query)
+ params = CGI.parse(URI.parse(link[:href]).query)
expect(params).to include('private_token' => [user.private_token])
expect(params).to include('state' => ['opened'])
diff --git a/spec/features/atom/issues_spec.rb b/spec/features/atom/issues_spec.rb
index 863412d18eb..a01a050a013 100644
--- a/spec/features/atom/issues_spec.rb
+++ b/spec/features/atom/issues_spec.rb
@@ -43,7 +43,7 @@ describe 'Issues Feed', feature: true do
:atom, private_token: user.private_token, state: 'opened', assignee_id: user.id)
link = find('link[type="application/atom+xml"]')
- params = CGI::parse(URI.parse(link[:href]).query)
+ params = CGI.parse(URI.parse(link[:href]).query)
expect(params).to include('private_token' => [user.private_token])
expect(params).to include('state' => ['opened'])
@@ -54,7 +54,7 @@ describe 'Issues Feed', feature: true do
visit issues_group_path(group, :atom, private_token: user.private_token, state: 'opened', assignee_id: user.id)
link = find('link[type="application/atom+xml"]')
- params = CGI::parse(URI.parse(link[:href]).query)
+ params = CGI.parse(URI.parse(link[:href]).query)
expect(params).to include('private_token' => [user.private_token])
expect(params).to include('state' => ['opened'])
diff --git a/spec/features/boards/add_issues_modal_spec.rb b/spec/features/boards/add_issues_modal_spec.rb
index 2875fc1e533..a3e24bb5ffa 100644
--- a/spec/features/boards/add_issues_modal_spec.rb
+++ b/spec/features/boards/add_issues_modal_spec.rb
@@ -49,6 +49,12 @@ describe 'Issue Boards add issue modal', :feature, :js do
expect(page).not_to have_selector('.add-issues-modal')
end
+
+ it 'does not show tooltip on add issues button' do
+ button = page.find('.issue-boards-search button', text: 'Add issues')
+
+ expect(button[:title]).not_to eq("Please add a list to your board first")
+ end
end
context 'issues list' do
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 34f47daf0e5..e247bfa2980 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -3,6 +3,7 @@ require 'rails_helper'
describe 'Issue Boards', feature: true, js: true do
include WaitForAjax
include WaitForVueResource
+ include DragTo
let(:project) { create(:empty_project, :public) }
let(:board) { create(:board, project: project) }
@@ -27,6 +28,12 @@ describe 'Issue Boards', feature: true, js: true do
expect(page).to have_content('Welcome to your Issue Board!')
end
+ it 'shows tooltip on add issues button' do
+ button = page.find('.issue-boards-search button', text: 'Add issues')
+
+ expect(button[:"data-original-title"]).to eq("Please add a list to your board first")
+ end
+
it 'hides the blank state when clicking nevermind button' do
page.within(find('.board-blank-state')) do
click_button("Nevermind, I'll use my own")
@@ -188,7 +195,7 @@ describe 'Issue Boards', feature: true, js: true do
end
it 'moves issue to done' do
- drag_to(list_from_index: 0, list_to_index: 2)
+ drag(list_from_index: 0, list_to_index: 2)
wait_for_board_cards(1, 7)
wait_for_board_cards(2, 2)
@@ -201,7 +208,7 @@ describe 'Issue Boards', feature: true, js: true do
end
it 'removes all of the same issue to done' do
- drag_to(list_from_index: 0, list_to_index: 2)
+ drag(list_from_index: 0, list_to_index: 2)
wait_for_board_cards(1, 7)
wait_for_board_cards(2, 2)
@@ -215,7 +222,7 @@ describe 'Issue Boards', feature: true, js: true do
context 'lists' do
it 'changes position of list' do
- drag_to(list_from_index: 1, list_to_index: 0, selector: '.board-header')
+ drag(list_from_index: 1, list_to_index: 0, selector: '.board-header')
wait_for_board_cards(1, 2)
wait_for_board_cards(2, 8)
@@ -226,7 +233,7 @@ describe 'Issue Boards', feature: true, js: true do
end
it 'issue moves between lists' do
- drag_to(list_from_index: 0, card_index: 1, list_to_index: 1)
+ drag(list_from_index: 0, from_index: 1, list_to_index: 1)
wait_for_board_cards(1, 7)
wait_for_board_cards(2, 2)
@@ -237,7 +244,7 @@ describe 'Issue Boards', feature: true, js: true do
end
it 'issue moves between lists' do
- drag_to(list_from_index: 1, list_to_index: 0)
+ drag(list_from_index: 1, list_to_index: 0)
wait_for_board_cards(1, 9)
wait_for_board_cards(2, 1)
@@ -248,7 +255,7 @@ describe 'Issue Boards', feature: true, js: true do
end
it 'issue moves from done' do
- drag_to(list_from_index: 2, list_to_index: 1)
+ drag(list_from_index: 2, list_to_index: 1)
expect(find('.board:nth-child(2)')).to have_content(issue8.title)
@@ -615,14 +622,13 @@ describe 'Issue Boards', feature: true, js: true do
end
end
- def drag_to(list_from_index: 0, card_index: 0, to_index: 0, list_to_index: 0, selector: '.board-list')
- evaluate_script("simulateDrag({scrollable: document.getElementById('board-app'), from: {el: $('#{selector}').eq(#{list_from_index}).get(0), index: #{card_index}}, to: {el: $('.board-list').eq(#{list_to_index}).get(0), index: #{to_index}}});")
-
- Timeout.timeout(Capybara.default_max_wait_time) do
- loop until page.evaluate_script('window.SIMULATE_DRAG_ACTIVE').zero?
- end
-
- wait_for_vue_resource
+ def drag(selector: '.board-list', list_from_index: 0, from_index: 0, to_index: 0, list_to_index: 0)
+ drag_to(selector: selector,
+ scrollable: '#board-app',
+ list_from_index: list_from_index,
+ from_index: from_index,
+ to_index: to_index,
+ list_to_index: list_to_index)
end
def wait_for_board_cards(board_number, expected_cards)
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index 9cc50167395..59e87b3f69c 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -15,8 +15,11 @@ describe 'Issue Boards', feature: true, js: true do
let!(:issue2) { create(:labeled_issue, project: project, labels: [development, stretch]) }
let(:board) { create(:board, project: project) }
let!(:list) { create(:list, board: board, label: development, position: 0) }
+ let(:card) { first('.board').first('.card') }
before do
+ Timecop.freeze
+
project.team << [user, :master]
login_as(user)
@@ -25,44 +28,38 @@ describe 'Issue Boards', feature: true, js: true do
wait_for_vue_resource
end
+ after do
+ Timecop.return
+ end
+
it 'shows sidebar when clicking issue' do
- page.within(first('.board')) do
- first('.card').click
- end
+ click_card(card)
expect(page).to have_selector('.issue-boards-sidebar')
end
it 'closes sidebar when clicking issue' do
- page.within(first('.board')) do
- first('.card').click
- end
+ click_card(card)
expect(page).to have_selector('.issue-boards-sidebar')
- page.within(first('.board')) do
- first('.card').click
- end
+ click_card(card)
expect(page).not_to have_selector('.issue-boards-sidebar')
end
it 'closes sidebar when clicking close button' do
- page.within(first('.board')) do
- first('.card').click
- end
+ click_card(card)
expect(page).to have_selector('.issue-boards-sidebar')
- find('.gutter-toggle').click
+ find('.gutter-toggle').trigger('click')
expect(page).not_to have_selector('.issue-boards-sidebar')
end
it 'shows issue details when sidebar is open' do
- page.within(first('.board')) do
- first('.card').click
- end
+ click_card(card)
page.within('.issue-boards-sidebar') do
expect(page).to have_content(issue2.title)
@@ -70,15 +67,15 @@ describe 'Issue Boards', feature: true, js: true do
end
end
- it 'removes card from board when clicking remove button' do
- page.within(first('.board')) do
- first('.card').click
- end
+ it 'removes card from board when clicking ' do
+ click_card(card)
page.within('.issue-boards-sidebar') do
click_button 'Remove from board'
end
+ wait_for_vue_resource
+
page.within(first('.board')) do
expect(page).to have_selector('.card', count: 1)
end
@@ -86,9 +83,7 @@ describe 'Issue Boards', feature: true, js: true do
context 'assignee' do
it 'updates the issues assignee' do
- page.within(first('.board')) do
- first('.card').click
- end
+ click_card(card)
page.within('.assignee') do
click_link 'Edit'
@@ -104,17 +99,12 @@ describe 'Issue Boards', feature: true, js: true do
expect(page).to have_content(user.name)
end
- page.within(first('.board')) do
- page.within(first('.card')) do
- expect(page).to have_selector('.avatar')
- end
- end
+ expect(card).to have_selector('.avatar')
end
it 'removes the assignee' do
- page.within(first('.board')) do
- find('.card:nth-child(2)').click
- end
+ card_two = first('.board').find('.card:nth-child(2)')
+ click_card(card_two)
page.within('.assignee') do
click_link 'Edit'
@@ -130,17 +120,11 @@ describe 'Issue Boards', feature: true, js: true do
expect(page).to have_content('No assignee')
end
- page.within(first('.board')) do
- page.within(find('.card:nth-child(2)')) do
- expect(page).not_to have_selector('.avatar')
- end
- end
+ expect(card_two).not_to have_selector('.avatar')
end
it 'assignees to current user' do
- page.within(first('.board')) do
- first('.card').click
- end
+ click_card(card)
page.within(find('.assignee')) do
expect(page).to have_content('No assignee')
@@ -152,17 +136,11 @@ describe 'Issue Boards', feature: true, js: true do
expect(page).to have_content(user.name)
end
- page.within(first('.board')) do
- page.within(first('.card')) do
- expect(page).to have_selector('.avatar')
- end
- end
+ expect(card).to have_selector('.avatar')
end
it 'resets assignee dropdown' do
- page.within(first('.board')) do
- first('.card').click
- end
+ click_card(card)
page.within('.assignee') do
click_link 'Edit'
@@ -192,9 +170,7 @@ describe 'Issue Boards', feature: true, js: true do
context 'milestone' do
it 'adds a milestone' do
- page.within(first('.board')) do
- first('.card').click
- end
+ click_card(card)
page.within('.milestone') do
click_link 'Edit'
@@ -212,9 +188,7 @@ describe 'Issue Boards', feature: true, js: true do
end
it 'removes a milestone' do
- page.within(first('.board')) do
- find('.card:nth-child(2)').click
- end
+ click_card(card)
page.within('.milestone') do
click_link 'Edit'
@@ -234,14 +208,12 @@ describe 'Issue Boards', feature: true, js: true do
context 'due date' do
it 'updates due date' do
- page.within(first('.board')) do
- first('.card').click
- end
+ click_card(card)
page.within('.due_date') do
click_link 'Edit'
- click_link Date.today.day
+ click_button Date.today.day
wait_for_vue_resource
@@ -252,9 +224,7 @@ describe 'Issue Boards', feature: true, js: true do
context 'labels' do
it 'adds a single label' do
- page.within(first('.board')) do
- first('.card').click
- end
+ click_card(card)
page.within('.labels') do
click_link 'Edit'
@@ -273,18 +243,12 @@ describe 'Issue Boards', feature: true, js: true do
end
end
- page.within(first('.board')) do
- page.within(first('.card')) do
- expect(page).to have_selector('.label', count: 2)
- expect(page).to have_content(bug.title)
- end
- end
+ expect(card).to have_selector('.label', count: 2)
+ expect(card).to have_content(bug.title)
end
it 'adds a multiple labels' do
- page.within(first('.board')) do
- first('.card').click
- end
+ click_card(card)
page.within('.labels') do
click_link 'Edit'
@@ -305,19 +269,13 @@ describe 'Issue Boards', feature: true, js: true do
end
end
- page.within(first('.board')) do
- page.within(first('.card')) do
- expect(page).to have_selector('.label', count: 3)
- expect(page).to have_content(bug.title)
- expect(page).to have_content(regression.title)
- end
- end
+ expect(card).to have_selector('.label', count: 3)
+ expect(card).to have_content(bug.title)
+ expect(card).to have_content(regression.title)
end
it 'removes a label' do
- page.within(first('.board')) do
- first('.card').click
- end
+ click_card(card)
page.within('.labels') do
click_link 'Edit'
@@ -336,20 +294,14 @@ describe 'Issue Boards', feature: true, js: true do
end
end
- page.within(first('.board')) do
- page.within(first('.card')) do
- expect(page).not_to have_selector('.label')
- expect(page).not_to have_content(stretch.title)
- end
- end
+ expect(card).not_to have_selector('.label')
+ expect(card).not_to have_content(stretch.title)
end
end
context 'subscription' do
it 'changes issue subscription' do
- page.within(first('.board')) do
- first('.card').click
- end
+ click_card(card)
page.within('.subscription') do
click_button 'Subscribe'
@@ -358,4 +310,19 @@ describe 'Issue Boards', feature: true, js: true do
end
end
end
+
+ def click_card(card)
+ page.within(card) do
+ first('.card-number').click
+ end
+
+ wait_for_sidebar
+ end
+
+ def wait_for_sidebar
+ # loop until the CSS transition is complete
+ Timeout.timeout(0.5) do
+ loop until evaluate_script('$(".right-sidebar").outerWidth()') == 290
+ end
+ end
end
diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb
index 3e0b6364e0d..35d090c4b7f 100644
--- a/spec/features/calendar_spec.rb
+++ b/spec/features/calendar_spec.rb
@@ -1,9 +1,11 @@
require 'spec_helper'
-feature 'Contributions Calendar', js: true, feature: true do
+feature 'Contributions Calendar', :feature, :js do
include WaitForAjax
+ let(:user) { create(:user) }
let(:contributed_project) { create(:project, :public) }
+ let(:issue_note) { create(:note, project: contributed_project) }
# Ex/ Sunday Jan 1, 2016
date_format = '%A %b %-d, %Y'
@@ -12,31 +14,31 @@ feature 'Contributions Calendar', js: true, feature: true do
issue_params = { title: issue_title }
def get_cell_color_selector(contributions)
- contribution_cell = '.user-contrib-cell'
- activity_colors = Array['#ededed', '#acd5f2', '#7fa8c9', '#527ba0', '#254e77']
- activity_colors_index = 0
-
- if contributions > 0 && contributions < 10
- activity_colors_index = 1
- elsif contributions >= 10 && contributions < 20
- activity_colors_index = 2
- elsif contributions >= 20 && contributions < 30
- activity_colors_index = 3
- elsif contributions >= 30
- activity_colors_index = 4
- end
+ activity_colors = %w[#ededed #acd5f2 #7fa8c9 #527ba0 #254e77]
+ # We currently don't actually test the cases with contributions >= 20
+ activity_colors_index =
+ if contributions > 0 && contributions < 10
+ 1
+ elsif contributions >= 10 && contributions < 20
+ 2
+ elsif contributions >= 20 && contributions < 30
+ 3
+ elsif contributions >= 30
+ 4
+ else
+ 0
+ end
- "#{contribution_cell}[fill='#{activity_colors[activity_colors_index]}']"
+ ".user-contrib-cell[fill='#{activity_colors[activity_colors_index]}']"
end
def get_cell_date_selector(contributions, date)
- contribution_text = 'No contributions'
-
- if contributions === 1
- contribution_text = '1 contribution'
- elsif contributions > 1
- contribution_text = "#{contributions} contributions"
- end
+ contribution_text =
+ if contributions.zero?
+ 'No contributions'
+ else
+ "#{contributions} #{'contribution'.pluralize(contributions)}"
+ end
"#{get_cell_color_selector(contributions)}[data-original-title='#{contribution_text}<br />#{date}']"
end
@@ -45,129 +47,155 @@ feature 'Contributions Calendar', js: true, feature: true do
push_params = {
project: contributed_project,
action: Event::PUSHED,
- author_id: @user.id,
+ author_id: user.id,
data: { commit_count: 3 }
}
Event.create(push_params)
end
- def get_first_cell_content
- find('.user-calendar-activities').text
- end
+ def note_comment_contribution
+ note_comment_params = {
+ project: contributed_project,
+ action: Event::COMMENTED,
+ target: issue_note,
+ author_id: user.id
+ }
- before do
- login_as :user
- visit @user.username
- wait_for_ajax
+ Event.create(note_comment_params)
end
- it 'displays calendar', js: true do
- expect(page).to have_css('.js-contrib-calendar')
+ def selected_day_activities
+ find('.user-calendar-activities').text
end
- describe 'select calendar day', js: true do
- let(:cells) { page.all('.user-contrib-cell') }
- let(:first_cell_content_before) { get_first_cell_content }
+ before do
+ login_as user
+ end
+ describe 'calendar day selection' do
before do
- cells[0].click
+ visit user.username
wait_for_ajax
- first_cell_content_before
end
- it 'displays calendar day activities', js: true do
- expect(get_first_cell_content).not_to eq('')
+ it 'displays calendar' do
+ expect(page).to have_css('.js-contrib-calendar')
end
- describe 'select another calendar day', js: true do
+ describe 'select calendar day' do
+ let(:cells) { page.all('.user-contrib-cell') }
+
before do
- cells[1].click
+ cells[0].click
wait_for_ajax
+ @first_day_activities = selected_day_activities
end
- it 'displays different calendar day activities', js: true do
- expect(get_first_cell_content).not_to eq(first_cell_content_before)
+ it 'displays calendar day activities' do
+ expect(selected_day_activities).not_to be_empty
end
- end
- describe 'deselect calendar day', js: true do
- before do
- cells[0].click
- wait_for_ajax
+ describe 'select another calendar day' do
+ before do
+ cells[1].click
+ wait_for_ajax
+ end
+
+ it 'displays different calendar day activities' do
+ expect(selected_day_activities).not_to eq(@first_day_activities)
+ end
end
- it 'hides calendar day activities', js: true do
- expect(get_first_cell_content).to eq('')
+ describe 'deselect calendar day' do
+ before do
+ cells[0].click
+ wait_for_ajax
+ end
+
+ it 'hides calendar day activities' do
+ expect(selected_day_activities).to be_empty
+ end
end
end
end
- describe '1 calendar activity' do
- before do
- Issues::CreateService.new(contributed_project, @user, issue_params).execute
- visit @user.username
- wait_for_ajax
+ describe 'calendar daily activities' do
+ shared_context 'visit user page' do
+ before do
+ visit user.username
+ wait_for_ajax
+ end
end
- it 'displays calendar activity log', js: true do
- expect(find('.content_list .event-note')).to have_content issue_title
- end
+ shared_examples 'a day with activity' do |contribution_count:|
+ include_context 'visit user page'
- it 'displays calendar activity square color for 1 contribution', js: true do
- expect(page).to have_selector(get_cell_color_selector(1), count: 1)
- end
+ it 'displays calendar activity square color for 1 contribution' do
+ expect(page).to have_selector(get_cell_color_selector(contribution_count), count: 1)
+ end
- it 'displays calendar activity square on the correct date', js: true do
- today = Date.today.strftime(date_format)
- expect(page).to have_selector(get_cell_date_selector(1, today), count: 1)
+ it 'displays calendar activity square on the correct date' do
+ today = Date.today.strftime(date_format)
+ expect(page).to have_selector(get_cell_date_selector(contribution_count, today), count: 1)
+ end
end
- end
- describe '10 calendar activities' do
- before do
- (0..9).each do |i|
- push_code_contribution()
+ describe '1 issue creation calendar activity' do
+ before do
+ Issues::CreateService.new(contributed_project, user, issue_params).execute
end
- visit @user.username
- wait_for_ajax
- end
+ it_behaves_like 'a day with activity', contribution_count: 1
- it 'displays calendar activity square color for 10 contributions', js: true do
- expect(page).to have_selector(get_cell_color_selector(10), count: 1)
- end
+ describe 'issue title is shown on activity page' do
+ include_context 'visit user page'
- it 'displays calendar activity square on the correct date', js: true do
- today = Date.today.strftime(date_format)
- expect(page).to have_selector(get_cell_date_selector(10, today), count: 1)
+ it 'displays calendar activity log' do
+ expect(find('.content_list .event-note')).to have_content issue_title
+ end
+ end
end
- end
- describe 'calendar activity on two days' do
- before do
- push_code_contribution()
-
- Timecop.freeze(Date.yesterday)
- Issues::CreateService.new(contributed_project, @user, issue_params).execute
- Timecop.return
+ describe '1 comment calendar activity' do
+ before do
+ note_comment_contribution
+ end
- visit @user.username
- wait_for_ajax
+ it_behaves_like 'a day with activity', contribution_count: 1
end
- it 'displays calendar activity squares for both days', js: true do
- expect(page).to have_selector(get_cell_color_selector(1), count: 2)
- end
+ describe '10 calendar activities' do
+ before do
+ 10.times { push_code_contribution }
+ end
- it 'displays calendar activity square for yesterday', js: true do
- yesterday = Date.yesterday.strftime(date_format)
- expect(page).to have_selector(get_cell_date_selector(1, yesterday), count: 1)
+ it_behaves_like 'a day with activity', contribution_count: 10
end
- it 'displays calendar activity square for today', js: true do
- today = Date.today.strftime(date_format)
- expect(page).to have_selector(get_cell_date_selector(1, today), count: 1)
+ describe 'calendar activity on two days' do
+ before do
+ push_code_contribution
+
+ Timecop.freeze(Date.yesterday) do
+ Issues::CreateService.new(contributed_project, user, issue_params).execute
+ end
+ end
+ include_context 'visit user page'
+
+ it 'displays calendar activity squares for both days' do
+ expect(page).to have_selector(get_cell_color_selector(1), count: 2)
+ end
+
+ it 'displays calendar activity square for yesterday' do
+ yesterday = Date.yesterday.strftime(date_format)
+ expect(page).to have_selector(get_cell_date_selector(1, yesterday), count: 1)
+ end
+
+ it 'displays calendar activity square for today' do
+ today = Date.today.strftime(date_format)
+ expect(page).to have_selector(get_cell_date_selector(1, today), count: 1)
+ end
end
end
end
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index 8f561c8f90b..324ede798fe 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -153,7 +153,7 @@ describe 'Commits' do
expect(page).to have_content pipeline.git_author_name
expect(page).to have_link('Download artifacts')
expect(page).not_to have_link('Cancel running')
- expect(page).not_to have_link('Retry failed')
+ expect(page).not_to have_link('Retry')
end
end
@@ -172,7 +172,7 @@ describe 'Commits' do
expect(page).to have_content pipeline.git_author_name
expect(page).not_to have_link('Download artifacts')
expect(page).not_to have_link('Cancel running')
- expect(page).not_to have_link('Retry failed')
+ expect(page).not_to have_link('Retry')
end
end
end
diff --git a/spec/features/copy_as_gfm_spec.rb b/spec/features/copy_as_gfm_spec.rb
index f3a5b565122..fec86128d03 100644
--- a/spec/features/copy_as_gfm_spec.rb
+++ b/spec/features/copy_as_gfm_spec.rb
@@ -251,6 +251,8 @@ describe 'Copy as GFM', feature: true, js: true do
'SanitizationFilter',
<<-GFM.strip_heredoc
+ <a name="named-anchor"></a>
+
<sub>sub</sub>
<dl>
diff --git a/spec/features/dashboard/active_tab_spec.rb b/spec/features/dashboard/active_tab_spec.rb
index 7d59fcac517..ae750be4d4a 100644
--- a/spec/features/dashboard/active_tab_spec.rb
+++ b/spec/features/dashboard/active_tab_spec.rb
@@ -1,14 +1,15 @@
require 'spec_helper'
-RSpec.describe 'Dashboard Active Tab', feature: true do
+RSpec.describe 'Dashboard Active Tab', js: true, feature: true do
before do
login_as :user
end
shared_examples 'page has active tab' do |title|
it "#{title} tab" do
- expect(page).to have_selector('.nav-sidebar li.active', count: 1)
- expect(find('.nav-sidebar li.active')).to have_content(title)
+ find('.global-dropdown-toggle').trigger('click')
+ expect(page).to have_selector('.global-dropdown-menu li.active', count: 1)
+ expect(find('.global-dropdown-menu li.active')).to have_content(title)
end
end
diff --git a/spec/features/dashboard/issuables_counter_spec.rb b/spec/features/dashboard/issuables_counter_spec.rb
index 41dcfe439c2..a1718912fc6 100644
--- a/spec/features/dashboard/issuables_counter_spec.rb
+++ b/spec/features/dashboard/issuables_counter_spec.rb
@@ -35,8 +35,9 @@ describe 'Navigation bar counter', feature: true, js: true, caching: true do
end
def expect_counters(issuable_type, count)
- dashboard_count = find('li.active span.badge')
- nav_count = find(".dashboard-shortcuts-#{issuable_type} span.count")
+ dashboard_count = find('li.active')
+ find('.global-dropdown-toggle').click
+ nav_count = find(".dashboard-shortcuts-#{issuable_type}")
expect(nav_count).to have_content(count)
expect(dashboard_count).to have_content(count)
diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb
new file mode 100644
index 00000000000..2db1cf71209
--- /dev/null
+++ b/spec/features/dashboard/issues_spec.rb
@@ -0,0 +1,48 @@
+require 'spec_helper'
+
+RSpec.describe 'Dashboard Issues', feature: true do
+ let(:current_user) { create :user }
+ let(:public_project) { create(:empty_project, :public) }
+ let(:project) do
+ create(:empty_project) do |project|
+ project.team << [current_user, :master]
+ end
+ end
+
+ let!(:authored_issue) { create :issue, author: current_user, project: project }
+ let!(:authored_issue_on_public_project) { create :issue, author: current_user, project: public_project }
+ let!(:assigned_issue) { create :issue, assignee: current_user, project: project }
+ let!(:other_issue) { create :issue, project: project }
+
+ before do
+ login_as(current_user)
+
+ visit issues_dashboard_path(assignee_id: current_user.id)
+ end
+
+ it 'shows issues assigned to current user' do
+ expect(page).to have_content(assigned_issue.title)
+ expect(page).not_to have_content(authored_issue.title)
+ expect(page).not_to have_content(other_issue.title)
+ end
+
+ it 'shows issues when current user is author', js: true do
+ find('#assignee_id', visible: false).set('')
+ find('.js-author-search', match: :first).click
+ find('.dropdown-menu-author li a', match: :first, text: current_user.to_reference).click
+
+ expect(page).to have_content(authored_issue.title)
+ expect(page).to have_content(authored_issue_on_public_project.title)
+ expect(page).not_to have_content(assigned_issue.title)
+ expect(page).not_to have_content(other_issue.title)
+ end
+
+ it 'shows all issues' do
+ click_link('Reset filters')
+
+ expect(page).to have_content(authored_issue.title)
+ expect(page).to have_content(authored_issue_on_public_project.title)
+ expect(page).to have_content(assigned_issue.title)
+ expect(page).to have_content(other_issue.title)
+ end
+end
diff --git a/spec/features/dashboard/project_member_activity_index_spec.rb b/spec/features/dashboard/project_member_activity_index_spec.rb
index ba77093a6d4..49d93db58a9 100644
--- a/spec/features/dashboard/project_member_activity_index_spec.rb
+++ b/spec/features/dashboard/project_member_activity_index_spec.rb
@@ -12,7 +12,7 @@ feature 'Project member activity', feature: true, js: true do
def visit_activities_and_wait_with_event(event_type)
Event.create(project: project, author_id: user.id, action: event_type)
- visit activity_namespace_project_path(project.namespace.path, project.path)
+ visit activity_namespace_project_path(project.namespace, project)
wait_for_ajax
end
diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb
index d9be4e5dbdd..62a2c54c94c 100644
--- a/spec/features/dashboard/shortcuts_spec.rb
+++ b/spec/features/dashboard/shortcuts_spec.rb
@@ -10,20 +10,20 @@ feature 'Dashboard shortcuts', feature: true, js: true do
find('body').native.send_key('g')
find('body').native.send_key('p')
- ensure_active_main_tab('Projects')
+ check_page_title('Projects')
find('body').native.send_key('g')
find('body').native.send_key('i')
- ensure_active_main_tab('Issues')
+ check_page_title('Issues')
find('body').native.send_key('g')
find('body').native.send_key('m')
- ensure_active_main_tab('Merge Requests')
+ check_page_title('Merge Requests')
end
- def ensure_active_main_tab(content)
- expect(find('.nav-sidebar li.active')).to have_content(content)
+ def check_page_title(title)
+ expect(find('.header-content .title')).to have_content(title)
end
end
diff --git a/spec/features/dashboard_issues_spec.rb b/spec/features/dashboard_issues_spec.rb
index b898f9bc64f..aa75e1140f6 100644
--- a/spec/features/dashboard_issues_spec.rb
+++ b/spec/features/dashboard_issues_spec.rb
@@ -49,9 +49,9 @@ describe "Dashboard Issues filtering", feature: true, js: true do
visit_issues(milestone_title: '', assignee_id: user.id)
link = find('.nav-controls a', text: 'Subscribe')
- params = CGI::parse(URI.parse(link[:href]).query)
+ params = CGI.parse(URI.parse(link[:href]).query)
auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
- auto_discovery_params = CGI::parse(URI.parse(auto_discovery_link[:href]).query)
+ auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query)
expect(params).to include('private_token' => [user.private_token])
expect(params).to include('milestone_title' => [''])
diff --git a/spec/features/environment_spec.rb b/spec/features/environment_spec.rb
index 2f49e89b4e4..c203e1f20c1 100644
--- a/spec/features/environment_spec.rb
+++ b/spec/features/environment_spec.rb
@@ -101,6 +101,22 @@ feature 'Environment', :feature do
scenario 'it shows the terminal button' do
expect(page).to have_terminal_button
end
+
+ context 'web terminal', :js do
+ before do
+ # Stub #terminals as it causes js-enabled feature specs to render the page incorrectly
+ allow_any_instance_of(Environment).to receive(:terminals) { nil }
+ visit terminal_namespace_project_environment_path(project.namespace, project, environment)
+ end
+
+ it 'displays a web terminal' do
+ expect(page).to have_selector('#terminal')
+ end
+
+ it 'displays a link to the environment external url' do
+ expect(page).to have_link(nil, href: environment.external_url)
+ end
+ end
end
context 'for developer' do
diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb
index 8b3e2fa93a2..8c64b050e19 100644
--- a/spec/features/expand_collapse_diffs_spec.rb
+++ b/spec/features/expand_collapse_diffs_spec.rb
@@ -72,8 +72,8 @@ feature 'Expand and collapse diffs', js: true, feature: true do
it 'collapses large diffs for renamed files by default' do
expect(large_diff_renamed).not_to have_selector('.code')
expect(large_diff_renamed).to have_selector('.nothing-here-block')
- expect(large_diff_renamed).to have_selector('.file-title .deletion')
- expect(large_diff_renamed).to have_selector('.file-title .addition')
+ expect(large_diff_renamed).to have_selector('.js-file-title .deletion')
+ expect(large_diff_renamed).to have_selector('.js-file-title .addition')
end
it 'shows non-renderable diffs as such immediately, regardless of their size' do
@@ -115,9 +115,9 @@ feature 'Expand and collapse diffs', js: true, feature: true do
context 'expanding a large diff' do
before do
# Wait for diffs
- find('.file-title', match: :first)
+ find('.js-file-title', match: :first)
# Click `large_diff.md` title
- all('.file-title')[1].click
+ all('.diff-toggle-caret')[1].click
wait_for_ajax
end
@@ -159,9 +159,9 @@ feature 'Expand and collapse diffs', js: true, feature: true do
context 'expanding the diff' do
before do
# Wait for diffs
- find('.file-title', match: :first)
+ find('.js-file-title', match: :first)
# Click `large_diff.md` title
- all('.file-title')[1].click
+ all('.diff-toggle-caret')[1].click
wait_for_ajax
end
@@ -181,9 +181,9 @@ feature 'Expand and collapse diffs', js: true, feature: true do
context 'collapsing an expanded diff' do
before do
# Wait for diffs
- find('.file-title', match: :first)
+ find('.js-file-title', match: :first)
# Click `small_diff.md` title
- all('.file-title')[3].click
+ all('.diff-toggle-caret')[3].click
end
it 'hides the diff content' do
@@ -194,9 +194,9 @@ feature 'Expand and collapse diffs', js: true, feature: true do
context 're-expanding the same diff' do
before do
# Wait for diffs
- find('.file-title', match: :first)
+ find('.js-file-title', match: :first)
# Click `small_diff.md` title
- all('.file-title')[3].click
+ all('.diff-toggle-caret')[3].click
end
it 'shows the diff content' do
@@ -290,9 +290,9 @@ feature 'Expand and collapse diffs', js: true, feature: true do
context 'collapsing an expanded diff' do
before do
# Wait for diffs
- find('.file-title', match: :first)
+ find('.js-file-title', match: :first)
# Click `small_diff.md` title
- all('.file-title')[3].click
+ all('.diff-toggle-caret')[3].click
end
it 'hides the diff content' do
@@ -303,9 +303,9 @@ feature 'Expand and collapse diffs', js: true, feature: true do
context 're-expanding the same diff' do
before do
# Wait for diffs
- find('.file-title', match: :first)
+ find('.js-file-title', match: :first)
# Click `small_diff.md` title
- all('.file-title')[3].click
+ all('.diff-toggle-caret')[3].click
end
it 'shows the diff content' do
diff --git a/spec/features/groups/members/list_spec.rb b/spec/features/groups/members/list_spec.rb
new file mode 100644
index 00000000000..14c193f7450
--- /dev/null
+++ b/spec/features/groups/members/list_spec.rb
@@ -0,0 +1,55 @@
+require 'spec_helper'
+
+feature 'Groups members list', feature: true do
+ let(:user1) { create(:user, name: 'John Doe') }
+ let(:user2) { create(:user, name: 'Mary Jane') }
+ let(:group) { create(:group) }
+ let(:nested_group) { create(:group, parent: group) }
+
+ background do
+ login_as(user1)
+ end
+
+ scenario 'show members from current group and parent' do
+ group.add_developer(user1)
+ nested_group.add_developer(user2)
+
+ visit group_group_members_path(nested_group)
+
+ expect(first_row.text).to include(user1.name)
+ expect(second_row.text).to include(user2.name)
+ end
+
+ scenario 'show user once if member of both current group and parent' do
+ group.add_developer(user1)
+ nested_group.add_developer(user1)
+
+ visit group_group_members_path(nested_group)
+
+ expect(first_row.text).to include(user1.name)
+ expect(second_row).to be_blank
+ end
+
+ it 'updates user to owner level', :js do
+ group.add_owner(user1)
+ group.add_developer(user2)
+
+ visit group_group_members_path(group)
+
+ page.within(second_row) do
+ click_button('Developer')
+
+ click_link('Owner')
+
+ expect(page).to have_button('Owner')
+ end
+ end
+
+ def first_row
+ page.all('ul.content-list > li')[0]
+ end
+
+ def second_row
+ page.all('ul.content-list > li')[1]
+ end
+end
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
index a515c92db37..37b7c20239f 100644
--- a/spec/features/groups_spec.rb
+++ b/spec/features/groups_spec.rb
@@ -45,6 +45,23 @@ feature 'Group', feature: true do
end
end
+ describe 'create a nested group' do
+ let(:group) { create(:group, path: 'foo') }
+
+ before do
+ visit subgroups_group_path(group)
+ click_link 'New Subgroup'
+ end
+
+ it 'creates a nested group' do
+ fill_in 'Group path', with: 'bar'
+ click_button 'Create group'
+
+ expect(current_path).to eq(group_path('foo/bar'))
+ expect(page).to have_content("Group 'bar' was successfully created.")
+ end
+ end
+
describe 'group edit' do
let(:group) { create(:group) }
let(:path) { edit_group_path(group) }
@@ -117,7 +134,7 @@ feature 'Group', feature: true do
visit path
click_link 'Subgroups'
- expect(page).to have_content(nested_group.full_name)
+ expect(page).to have_content(nested_group.name)
end
end
end
diff --git a/spec/features/help_pages_spec.rb b/spec/features/help_pages_spec.rb
index 40a1fced8d8..e0b2404e60a 100644
--- a/spec/features/help_pages_spec.rb
+++ b/spec/features/help_pages_spec.rb
@@ -33,4 +33,30 @@ describe 'Help Pages', feature: true do
it_behaves_like 'help page', prefix: '/gitlab'
end
end
+
+ context 'in a production environment with version check enabled', js: true do
+ before do
+ allow(Rails.env).to receive(:production?) { true }
+ allow(current_application_settings).to receive(:version_check_enabled) { true }
+ allow_any_instance_of(VersionCheck).to receive(:url) { '/version-check-url' }
+
+ login_as :user
+ visit help_path
+ end
+
+ it 'should display a version check image' do
+ expect(find('.js-version-status-badge')).to be_visible
+ end
+
+ it 'should have a src url' do
+ expect(find('.js-version-status-badge')['src']).to match(/\/version-check-url/)
+ end
+
+ it 'should hide the version check image if the image request fails' do
+ # We use '--load-images=no' with poltergeist so we must trigger manually
+ execute_script("$('.js-version-status-badge').trigger('error');")
+
+ expect(find('.js-version-status-badge', visible: false)).not_to be_visible
+ end
+ end
end
diff --git a/spec/features/issuables/issuable_list_spec.rb b/spec/features/issuables/issuable_list_spec.rb
new file mode 100644
index 00000000000..b90bf6268fd
--- /dev/null
+++ b/spec/features/issuables/issuable_list_spec.rb
@@ -0,0 +1,75 @@
+require 'rails_helper'
+
+describe 'issuable list', feature: true do
+ let(:project) { create(:empty_project) }
+ let(:user) { create(:user) }
+
+ issuable_types = [:issue, :merge_request]
+
+ before do
+ project.add_user(user, :developer)
+ login_as(user)
+ issuable_types.each { |type| create_issuables(type) }
+ end
+
+ issuable_types.each do |issuable_type|
+ it "avoids N+1 database queries for #{issuable_type.to_s.humanize.pluralize}" do
+ control_count = ActiveRecord::QueryRecorder.new { visit_issuable_list(issuable_type) }.count
+
+ create_issuables(issuable_type)
+
+ expect { visit_issuable_list(issuable_type) }.not_to exceed_query_limit(control_count)
+ end
+
+ it "counts upvotes, downvotes and notes count for each #{issuable_type.to_s.humanize}" do
+ visit_issuable_list(issuable_type)
+
+ expect(first('.fa-thumbs-up').find(:xpath, '..')).to have_content(1)
+ expect(first('.fa-thumbs-down').find(:xpath, '..')).to have_content(1)
+ expect(first('.fa-comments').find(:xpath, '..')).to have_content(2)
+ end
+ end
+
+ it "counts merge requests closing issues icons for each issue" do
+ visit_issuable_list(:issue)
+
+ expect(page).to have_selector('.icon-merge-request-unmerged', count: 1)
+ expect(first('.icon-merge-request-unmerged').find(:xpath, '..')).to have_content(1)
+ end
+
+ def visit_issuable_list(issuable_type)
+ if issuable_type == :issue
+ visit namespace_project_issues_path(project.namespace, project)
+ else
+ visit namespace_project_merge_requests_path(project.namespace, project)
+ end
+ end
+
+ def create_issuables(issuable_type)
+ 3.times do
+ issuable =
+ if issuable_type == :issue
+ create(:issue, project: project, author: user)
+ else
+ create(:merge_request, title: FFaker::Lorem.sentence, source_project: project, source_branch: FFaker::Name.name)
+ end
+
+ 2.times do
+ create(:note_on_issue, noteable: issuable, project: project, note: 'Test note')
+ end
+
+ create(:award_emoji, :downvote, awardable: issuable)
+ create(:award_emoji, :upvote, awardable: issuable)
+ end
+
+ if issuable_type == :issue
+ issue = Issue.reorder(:iid).first
+ merge_request = create(:merge_request,
+ title: FFaker::Lorem.sentence,
+ source_project: project,
+ source_branch: FFaker::Name.name)
+
+ MergeRequestsClosingIssues.create!(issue: issue, merge_request: merge_request)
+ end
+ end
+end
diff --git a/spec/features/issues/bulk_assignment_labels_spec.rb b/spec/features/issues/bulk_assignment_labels_spec.rb
index 832757b24d4..2f59630b4fb 100644
--- a/spec/features/issues/bulk_assignment_labels_spec.rb
+++ b/spec/features/issues/bulk_assignment_labels_spec.rb
@@ -55,7 +55,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
context 'to all issues' do
before do
check 'check_all_issues'
- open_labels_dropdown ['bug', 'feature']
+ open_labels_dropdown %w(bug feature)
update_issues
end
@@ -70,7 +70,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
context 'to a issue' do
before do
check "selected_issue_#{issue1.id}"
- open_labels_dropdown ['bug', 'feature']
+ open_labels_dropdown %w(bug feature)
update_issues
end
@@ -112,7 +112,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
visit namespace_project_issues_path(project.namespace, project)
check 'check_all_issues'
- unmark_labels_in_dropdown ['bug', 'feature']
+ unmark_labels_in_dropdown %w(bug feature)
update_issues
end
diff --git a/spec/features/issues/filtered_search/dropdown_label_spec.rb b/spec/features/issues/filtered_search/dropdown_label_spec.rb
index c6a88e1b7b0..ab3b868fd3a 100644
--- a/spec/features/issues/filtered_search/dropdown_label_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_label_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe 'Dropdown label', js: true, feature: true do
+ include FilteredSearchHelpers
+
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
let(:filtered_search) { find('.filtered-search') }
@@ -17,12 +19,6 @@ describe 'Dropdown label', js: true, feature: true do
let!(:long_label) { create(:label, project: project, title: 'this is a very long title this is a very long title this is a very long title this is a very long title this is a very long title') }
end
- def init_label_search
- filtered_search.set('label:')
- # This ensures the dropdown is shown
- expect(find(js_dropdown_label)).not_to have_css('.filter-dropdown-loading')
- end
-
def search_for_label(label)
init_label_search
filtered_search.send_keys(label)
diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb
index 3f70a6aa75f..0420e64d42c 100644
--- a/spec/features/issues/filtered_search/filter_issues_spec.rb
+++ b/spec/features/issues/filtered_search/filter_issues_spec.rb
@@ -1,6 +1,7 @@
require 'rails_helper'
describe 'Filter issues', js: true, feature: true do
+ include FilteredSearchHelpers
include WaitForAjax
let!(:group) { create(:group) }
@@ -17,19 +18,6 @@ describe 'Filter issues', js: true, feature: true do
let!(:multiple_words_label) { create(:label, project: project, title: "Two words") }
let!(:closed_issue) { create(:issue, title: 'bug that is closed', project: project, state: :closed) }
- let(:filtered_search) { find('.filtered-search') }
-
- def input_filtered_search(search_term, submit: true)
- filtered_search.set(search_term)
-
- if submit
- filtered_search.send_keys(:enter)
- end
- end
-
- def expect_filtered_search_input(input)
- expect(find('.filtered-search').value).to eq(input)
- end
def expect_no_issues_list
page.within '.issues-list' do
@@ -113,13 +101,11 @@ describe 'Filter issues', js: true, feature: true do
end
it 'filters issues by invalid author' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
it 'filters issues by multiple authors' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
end
@@ -158,8 +144,7 @@ describe 'Filter issues', js: true, feature: true do
end
it 'sorting' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
end
@@ -182,13 +167,11 @@ describe 'Filter issues', js: true, feature: true do
end
it 'filters issues by invalid assignee' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
it 'filters issues by multiple assignees' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
end
@@ -228,8 +211,7 @@ describe 'Filter issues', js: true, feature: true do
context 'sorting' do
it 'sorts' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
end
end
@@ -253,8 +235,7 @@ describe 'Filter issues', js: true, feature: true do
end
it 'filters issues by invalid label' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
it 'filters issues by multiple labels' do
@@ -429,8 +410,7 @@ describe 'Filter issues', js: true, feature: true do
context 'sorting' do
it 'sorts' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
end
end
@@ -456,13 +436,11 @@ describe 'Filter issues', js: true, feature: true do
end
it 'filters issues by invalid milestones' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
it 'filters issues by multiple milestones' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
it 'filters issues by milestone containing special characters' do
@@ -523,8 +501,7 @@ describe 'Filter issues', js: true, feature: true do
context 'sorting' do
it 'sorts' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
end
end
@@ -801,4 +778,26 @@ describe 'Filter issues', js: true, feature: true do
expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s])
end
end
+
+ context 'URL has a trailing slash' do
+ before do
+ visit "#{namespace_project_issues_path(project.namespace, project)}/"
+ end
+
+ it 'milestone dropdown loads milestones' do
+ input_filtered_search("milestone:", submit: false)
+
+ within('#js-dropdown-milestone') do
+ expect(page).to have_selector('.filter-dropdown .filter-dropdown-item', count: 2)
+ end
+ end
+
+ it 'label dropdown load labels' do
+ input_filtered_search("label:", submit: false)
+
+ within('#js-dropdown-label') do
+ expect(page).to have_selector('.filter-dropdown .filter-dropdown-item', count: 5)
+ end
+ end
+ end
end
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index 93139dc9e94..7135565294b 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -182,6 +182,20 @@ feature 'GFM autocomplete', feature: true, js: true do
expect(page).not_to have_selector('.atwho-view')
end
+ it 'triggers autocomplete after selecting a slash command' do
+ note = find('#note_note')
+ page.within '.timeline-content-form' do
+ note.native.send_keys('')
+ note.native.send_keys('/as')
+ note.click
+ end
+
+ find('.atwho-view li', text: '/assign').native.send_keys(:tab)
+
+ user_item = find('.atwho-view li', text: user.username)
+ expect(user_item).to have_content(user.username)
+ end
+
def expect_to_wrap(should_wrap, item, note, value)
expect(item).to have_content(value)
expect(item).not_to have_content("\"#{value}\"")
diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb
index 1eb981942ea..7b9d4534ada 100644
--- a/spec/features/issues/issue_sidebar_spec.rb
+++ b/spec/features/issues/issue_sidebar_spec.rb
@@ -7,9 +7,9 @@ feature 'Issue Sidebar', feature: true do
let(:project) { create(:project, :public) }
let(:issue) { create(:issue, project: project) }
let!(:user) { create(:user)}
+ let!(:label) { create(:label, project: project, title: 'bug') }
before do
- create(:label, project: project, title: 'bug')
login_as(user)
end
@@ -50,16 +50,6 @@ feature 'Issue Sidebar', feature: true do
visit_issue(project, issue)
end
- describe 'when clicking on edit labels', js: true do
- it 'shows dropdown option to create a new label' do
- find('.block.labels .edit-link').click
-
- page.within('.block.labels') do
- expect(page).to have_content 'Create new'
- end
- end
- end
-
context 'sidebar', js: true do
it 'changes size when the screen size is smaller' do
sidebar_selector = 'aside.right-sidebar.right-sidebar-collapsed'
@@ -77,36 +67,53 @@ feature 'Issue Sidebar', feature: true do
end
end
- context 'creating a new label', js: true do
- it 'shows option to crate a new label is present' do
+ context 'editing issue labels', js: true do
+ before do
page.within('.block.labels') do
find('.edit-link').click
+ end
+ end
+ it 'shows option to create a new label' do
+ page.within('.block.labels') do
expect(page).to have_content 'Create new'
end
end
- it 'shows dropdown switches to "create label" section' do
- page.within('.block.labels') do
- find('.edit-link').click
- click_link 'Create new'
+ context 'creating a new label', js: true do
+ before do
+ page.within('.block.labels') do
+ click_link 'Create new'
+ end
+ end
- expect(page).to have_content 'Create new label'
+ it 'shows dropdown switches to "create label" section' do
+ page.within('.block.labels') do
+ expect(page).to have_content 'Create new label'
+ end
end
- end
- it 'adds new label' do
- page.within('.block.labels') do
- find('.edit-link').click
- sleep 1
- click_link 'Create new'
+ it 'adds new label' do
+ page.within('.block.labels') do
+ fill_in 'new_label_name', with: 'wontfix'
+ page.find(".suggest-colors a", match: :first).click
+ click_button 'Create'
+
+ page.within('.dropdown-page-one') do
+ expect(page).to have_content 'wontfix'
+ end
+ end
+ end
- fill_in 'new_label_name', with: 'wontfix'
- page.find(".suggest-colors a", match: :first).click
- click_button 'Create'
+ it 'shows error message if label title is taken' do
+ page.within('.block.labels') do
+ fill_in 'new_label_name', with: label.title
+ page.find('.suggest-colors a', match: :first).click
+ click_button 'Create'
- page.within('.dropdown-page-one') do
- expect(page).to have_content 'wontfix'
+ page.within('.dropdown-page-two') do
+ expect(page).to have_content 'Title has already been taken'
+ end
end
end
end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index 394eb31aff8..1e0db4a0499 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -78,8 +78,8 @@ describe 'Issues', feature: true do
fill_in 'issue_description', with: 'bug description'
find('#issuable-due-date').click
- page.within '.ui-datepicker' do
- click_link date.day
+ page.within '.pika-single' do
+ click_button date.day
end
expect(find('#issuable-due-date').value).to eq date.to_s
@@ -110,8 +110,8 @@ describe 'Issues', feature: true do
fill_in 'issue_description', with: 'bug description'
find('#issuable-due-date').click
- page.within '.ui-datepicker' do
- click_link date.day
+ page.within '.pika-single' do
+ click_button date.day
end
expect(find('#issuable-due-date').value).to eq date.to_s
@@ -150,7 +150,7 @@ describe 'Issues', feature: true do
describe 'Filter issue' do
before do
- ['foobar', 'barbaz', 'gitlab'].each do |title|
+ %w(foobar barbaz gitlab).each do |title|
create(:issue,
author: @user,
assignee: @user,
@@ -382,7 +382,9 @@ describe 'Issues', feature: true do
it 'changes incoming email address token', js: true do
find('.issue-email-modal-btn').click
previous_token = find('input#issue_email').value
- find('.incoming-email-token-reset').click
+ find('.incoming-email-token-reset').trigger('click')
+
+ wait_for_ajax
expect(page).to have_no_field('issue_email', with: previous_token)
new_token = project1.new_issue_address(@user.reload)
@@ -575,6 +577,15 @@ describe 'Issues', feature: true do
expect(page.find_field("issue_description").value).to have_content 'banana_sample'
end
+
+ it 'adds double newline to end of attachment markdown' do
+ drop_in_dropzone test_image_file
+
+ # Wait for the file to upload
+ sleep 1
+
+ expect(page.find_field("issue_description").value).to match /\n\n$/
+ end
end
end
@@ -624,8 +635,8 @@ describe 'Issues', feature: true do
page.within '.due_date' do
click_link 'Edit'
- page.within '.ui-datepicker-calendar' do
- click_link date.day
+ page.within '.pika-single' do
+ click_button date.day
end
wait_for_ajax
@@ -635,11 +646,13 @@ describe 'Issues', feature: true do
end
it 'removes due date from issue' do
+ date = Date.today.at_beginning_of_month + 2.days
+
page.within '.due_date' do
click_link 'Edit'
- page.within '.ui-datepicker-calendar' do
- first('.ui-state-default').click
+ page.within '.pika-single' do
+ click_button date.day
end
wait_for_ajax
diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb
index ab7d89306db..ae609160e18 100644
--- a/spec/features/login_spec.rb
+++ b/spec/features/login_spec.rb
@@ -32,6 +32,22 @@ feature 'Login', feature: true do
end
end
+ describe 'with a blocked account' do
+ it 'prevents the user from logging in' do
+ user = create(:user, :blocked)
+
+ login_with(user)
+
+ expect(page).to have_content('Your account has been blocked.')
+ end
+
+ it 'does not update Devise trackable attributes' do
+ user = create(:user, :blocked)
+
+ expect { login_with(user) }.not_to change { user.reload.sign_in_count }
+ end
+ end
+
describe 'with two-factor authentication' do
def enter_code(code)
fill_in 'user_otp_attempt', with: code
diff --git a/spec/features/merge_requests/closes_issues_spec.rb b/spec/features/merge_requests/closes_issues_spec.rb
index c73065cdce1..eafcab6a0d7 100644
--- a/spec/features/merge_requests/closes_issues_spec.rb
+++ b/spec/features/merge_requests/closes_issues_spec.rb
@@ -10,10 +10,12 @@ feature 'Merge Request closing issues message', feature: true do
:merge_request,
:simple,
source_project: project,
- description: merge_request_description
+ description: merge_request_description,
+ title: merge_request_title
)
end
let(:merge_request_description) { 'Merge Request Description' }
+ let(:merge_request_title) { 'Merge Request Title' }
before do
project.team << [user, :master]
@@ -45,8 +47,32 @@ feature 'Merge Request closing issues message', feature: true do
end
end
- context 'closing some issues and mentioning, but not closing, others' do
- let(:merge_request_description) { "Description\n\ncloses #{issue_1.to_reference}\n\n refers to #{issue_2.to_reference}" }
+ context 'closing some issues in title and mentioning, but not closing, others' do
+ let(:merge_request_title) { "closes #{issue_1.to_reference}\n\n refers to #{issue_2.to_reference}" }
+
+ it 'does not display closing issue message' do
+ expect(page).to have_content("Accepting this merge request will close issue #{issue_1.to_reference}. Issue #{issue_2.to_reference} is mentioned but will not be closed.")
+ end
+ end
+
+ context 'closing issues using title but not mentioning any other issue' do
+ let(:merge_request_title) { "closing #{issue_1.to_reference}, #{issue_2.to_reference}" }
+
+ it 'does not display closing issue message' do
+ expect(page).to have_content("Accepting this merge request will close issues #{issue_1.to_reference} and #{issue_2.to_reference}")
+ end
+ end
+
+ context 'mentioning issues using title but not closing them' do
+ let(:merge_request_title) { "Refers to #{issue_1.to_reference} and #{issue_2.to_reference}" }
+
+ it 'does not display closing issue message' do
+ expect(page).to have_content("Issues #{issue_1.to_reference} and #{issue_2.to_reference} are mentioned but will not be closed.")
+ end
+ end
+
+ context 'closing some issues using title and mentioning, but not closing, others' do
+ let(:merge_request_title) { "closes #{issue_1.to_reference}\n\n refers to #{issue_2.to_reference}" }
it 'does not display closing issue message' do
expect(page).to have_content("Accepting this merge request will close issue #{issue_1.to_reference}. Issue #{issue_2.to_reference} is mentioned but will not be closed.")
diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb
index 5bc4ab2dfe5..18508a44184 100644
--- a/spec/features/merge_requests/conflicts_spec.rb
+++ b/spec/features/merge_requests/conflicts_spec.rb
@@ -141,7 +141,7 @@ feature 'Merge request conflict resolution', js: true, feature: true do
click_on 'Changes'
wait_for_ajax
- find('.click-to-expand').click
+ click_link 'Expand all'
wait_for_ajax
expect(page).to have_content('Gregor Samsa woke from troubled dreams')
@@ -154,7 +154,7 @@ feature 'Merge request conflict resolution', js: true, feature: true do
'conflict-binary-file' => 'when the conflicts contain a binary file',
'conflict-missing-side' => 'when the conflicts contain a file edited in one branch and deleted in another',
'conflict-non-utf8' => 'when the conflicts contain a non-UTF-8 file',
- }
+ }.freeze
UNRESOLVABLE_CONFLICTS.each do |source_branch, description|
context description do
diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb
index e853fb7e016..0832a3656a8 100644
--- a/spec/features/merge_requests/create_new_mr_spec.rb
+++ b/spec/features/merge_requests/create_new_mr_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
feature 'Create New Merge Request', feature: true, js: true do
+ include WaitForVueResource
+
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
@@ -99,6 +101,7 @@ feature 'Create New Merge Request', feature: true, js: true do
page.within('.merge-request') do
click_link 'Pipelines'
+ wait_for_vue_resource
expect(page).to have_content "##{pipeline.id}"
end
diff --git a/spec/features/merge_requests/filter_by_labels_spec.rb b/spec/features/merge_requests/filter_by_labels_spec.rb
index 4c60329865c..55f3c1863ff 100644
--- a/spec/features/merge_requests/filter_by_labels_spec.rb
+++ b/spec/features/merge_requests/filter_by_labels_spec.rb
@@ -1,6 +1,8 @@
require 'rails_helper'
feature 'Issue filtering by Labels', feature: true, js: true do
+ include FilteredSearchHelpers
+ include MergeRequestHelpers
include WaitForAjax
let(:project) { create(:project, :public) }
@@ -32,123 +34,77 @@ feature 'Issue filtering by Labels', feature: true, js: true do
context 'filter by label bug' do
before do
- select_labels('bug')
+ input_filtered_search('label:~bug')
end
it 'apply the filter' do
expect(page).to have_content "Bugfix1"
expect(page).to have_content "Bugfix2"
expect(page).not_to have_content "Feature1"
- expect(find('.filtered-labels')).to have_content "bug"
- expect(find('.filtered-labels')).not_to have_content "feature"
- expect(find('.filtered-labels')).not_to have_content "enhancement"
-
- find('.js-label-filter-remove').click
- wait_for_ajax
- expect(find('.filtered-labels', visible: false)).to have_no_content "bug"
end
end
context 'filter by label feature' do
before do
- select_labels('feature')
+ input_filtered_search('label:~feature')
end
it 'applies the filter' do
expect(page).to have_content "Feature1"
expect(page).not_to have_content "Bugfix2"
expect(page).not_to have_content "Bugfix1"
- expect(find('.filtered-labels')).to have_content "feature"
- expect(find('.filtered-labels')).not_to have_content "bug"
- expect(find('.filtered-labels')).not_to have_content "enhancement"
end
end
context 'filter by label enhancement' do
before do
- select_labels('enhancement')
+ input_filtered_search('label:~enhancement')
end
it 'applies the filter' do
expect(page).to have_content "Bugfix2"
expect(page).not_to have_content "Feature1"
expect(page).not_to have_content "Bugfix1"
- expect(find('.filtered-labels')).to have_content "enhancement"
- expect(find('.filtered-labels')).not_to have_content "bug"
- expect(find('.filtered-labels')).not_to have_content "feature"
end
end
context 'filter by label enhancement and bug in issues list' do
before do
- select_labels('bug', 'enhancement')
+ input_filtered_search('label:~bug label:~enhancement')
end
it 'applies the filters' do
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
expect(page).to have_content "Bugfix2"
expect(page).not_to have_content "Feature1"
- expect(find('.filtered-labels')).to have_content "bug"
- expect(find('.filtered-labels')).to have_content "enhancement"
- expect(find('.filtered-labels')).not_to have_content "feature"
-
- find('.js-label-filter-remove', match: :first).click
- wait_for_ajax
-
- expect(page).to have_content "Bugfix2"
- expect(page).not_to have_content "Feature1"
- expect(page).not_to have_content "Bugfix1"
- expect(find('.filtered-labels')).not_to have_content "bug"
- expect(find('.filtered-labels')).to have_content "enhancement"
- expect(find('.filtered-labels')).not_to have_content "feature"
end
end
- context 'remove filtered labels' do
+ context 'clear button' do
before do
- page.within '.labels-filter' do
- click_button 'Label'
- wait_for_ajax
- click_link 'bug'
- find('.dropdown-menu-close').click
- end
-
- page.within '.filtered-labels' do
- expect(page).to have_content 'bug'
- end
+ input_filtered_search('label:~bug')
end
it 'allows user to remove filtered labels' do
- first('.js-label-filter-remove').click
- wait_for_ajax
+ first('.clear-search').click
+ filtered_search.send_keys(:enter)
- expect(find('.filtered-labels', visible: false)).not_to have_content 'bug'
- expect(find('.labels-filter')).not_to have_content 'bug'
+ expect(page).to have_issuable_counts(open: 3, closed: 0, all: 3)
+ expect(page).to have_content "Bugfix2"
+ expect(page).to have_content "Feature1"
+ expect(page).to have_content "Bugfix1"
end
end
- context 'dropdown filtering' do
+ context 'filter dropdown' do
it 'filters by label name' do
- page.within '.labels-filter' do
- click_button 'Label'
- wait_for_ajax
- find('.dropdown-input input').set 'bug'
-
- page.within '.dropdown-content' do
- expect(page).not_to have_content 'enhancement'
- expect(page).to have_content 'bug'
- end
- end
- end
- end
+ init_label_search
+ filtered_search.send_keys('~bug')
- def select_labels(*labels)
- page.find('.js-label-select').click
- wait_for_ajax
- labels.each do |label|
- execute_script("$('.dropdown-menu-labels li:contains(\"#{label}\") a').click()")
+ page.within '.filter-dropdown' do
+ expect(page).not_to have_content 'enhancement'
+ expect(page).to have_content 'bug'
+ end
end
- page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
- wait_for_ajax
end
end
diff --git a/spec/features/merge_requests/filter_by_milestone_spec.rb b/spec/features/merge_requests/filter_by_milestone_spec.rb
index f6e9230c8da..5608cda28f8 100644
--- a/spec/features/merge_requests/filter_by_milestone_spec.rb
+++ b/spec/features/merge_requests/filter_by_milestone_spec.rb
@@ -1,10 +1,18 @@
require 'rails_helper'
feature 'Merge Request filtering by Milestone', feature: true do
+ include FilteredSearchHelpers
+ include MergeRequestHelpers
+
let(:project) { create(:project, :public) }
let!(:user) { create(:user)}
let(:milestone) { create(:milestone, project: project) }
+ def filter_by_milestone(title)
+ find(".js-milestone-select").click
+ find(".milestone-filter a", text: title).click
+ end
+
before do
project.team << [user, :master]
login_as(user)
@@ -15,42 +23,42 @@ feature 'Merge Request filtering by Milestone', feature: true do
create(:merge_request, :simple, source_project: project, milestone: milestone)
visit_merge_requests(project)
- filter_by_milestone(Milestone::None.title)
+ input_filtered_search('milestone:none')
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
expect(page).to have_css('.merge-request', count: 1)
end
context 'filters by upcoming milestone', js: true do
- it 'does not show issues with no expiry' do
+ it 'does not show merge requests with no expiry' do
create(:merge_request, :with_diffs, source_project: project)
create(:merge_request, :simple, source_project: project, milestone: milestone)
visit_merge_requests(project)
- filter_by_milestone(Milestone::Upcoming.title)
+ input_filtered_search('milestone:upcoming')
expect(page).to have_css('.merge-request', count: 0)
end
- it 'shows issues in future' do
+ it 'shows merge requests in future' do
milestone = create(:milestone, project: project, due_date: Date.tomorrow)
create(:merge_request, :with_diffs, source_project: project)
create(:merge_request, :simple, source_project: project, milestone: milestone)
visit_merge_requests(project)
- filter_by_milestone(Milestone::Upcoming.title)
+ input_filtered_search('milestone:upcoming')
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
expect(page).to have_css('.merge-request', count: 1)
end
- it 'does not show issues in past' do
+ it 'does not show merge requests in past' do
milestone = create(:milestone, project: project, due_date: Date.yesterday)
create(:merge_request, :with_diffs, source_project: project)
create(:merge_request, :simple, source_project: project, milestone: milestone)
visit_merge_requests(project)
- filter_by_milestone(Milestone::Upcoming.title)
+ input_filtered_search('milestone:upcoming')
expect(page).to have_css('.merge-request', count: 0)
end
@@ -61,7 +69,7 @@ feature 'Merge Request filtering by Milestone', feature: true do
create(:merge_request, :simple, source_project: project)
visit_merge_requests(project)
- filter_by_milestone(milestone.title)
+ input_filtered_search("milestone:%'#{milestone.title}'")
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
expect(page).to have_css('.merge-request', count: 1)
@@ -77,19 +85,10 @@ feature 'Merge Request filtering by Milestone', feature: true do
create(:merge_request, :simple, source_project: project)
visit_merge_requests(project)
- filter_by_milestone(milestone.title)
+ input_filtered_search("milestone:%\"#{milestone.title}\"")
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
expect(page).to have_css('.merge-request', count: 1)
end
end
-
- def visit_merge_requests(project)
- visit namespace_project_merge_requests_path(project.namespace, project)
- end
-
- def filter_by_milestone(title)
- find(".js-milestone-select").click
- find(".milestone-filter a", text: title).click
- end
end
diff --git a/spec/features/merge_requests/filter_merge_requests_spec.rb b/spec/features/merge_requests/filter_merge_requests_spec.rb
index 4642b5a530d..6579a88d4ab 100644
--- a/spec/features/merge_requests/filter_merge_requests_spec.rb
+++ b/spec/features/merge_requests/filter_merge_requests_spec.rb
@@ -1,11 +1,13 @@
require 'rails_helper'
describe 'Filter merge requests', feature: true do
+ include FilteredSearchHelpers
+ include MergeRequestHelpers
include WaitForAjax
let!(:project) { create(:project) }
let!(:group) { create(:group) }
- let!(:user) { create(:user)}
+ let!(:user) { create(:user) }
let!(:milestone) { create(:milestone, project: project) }
let!(:label) { create(:label, project: project) }
let!(:wontfix) { create(:label, project: project, title: "Won't fix") }
@@ -15,183 +17,134 @@ describe 'Filter merge requests', feature: true do
group.add_developer(user)
login_as(user)
create(:merge_request, source_project: project, target_project: project)
+
+ visit namespace_project_merge_requests_path(project.namespace, project)
end
describe 'for assignee from mr#index' do
- before do
- visit namespace_project_merge_requests_path(project.namespace, project)
+ let(:search_query) { "assignee:@#{user.username}" }
- find('.js-assignee-search').click
-
- find('.dropdown-menu-user-link', text: user.username).click
+ before do
+ input_filtered_search(search_query)
- wait_for_ajax
+ expect_mr_list_count(0)
end
context 'assignee', js: true do
it 'updates to current user' do
- expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name)
+ expect_filtered_search_input(search_query)
end
it 'does not change when closed link is clicked' do
find('.issues-state-filters a', text: "Closed").click
- expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name)
+ expect_filtered_search_input(search_query)
end
it 'does not change when all link is clicked' do
find('.issues-state-filters a', text: "All").click
- expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name)
+ expect_filtered_search_input(search_query)
end
end
end
describe 'for milestone from mr#index' do
- before do
- visit namespace_project_merge_requests_path(project.namespace, project)
-
- find('.js-milestone-select').click
+ let(:search_query) { "milestone:%#{milestone.title}" }
- find('.milestone-filter .dropdown-content a', text: milestone.title).click
+ before do
+ input_filtered_search(search_query)
- wait_for_ajax
+ expect_mr_list_count(0)
end
context 'milestone', js: true do
it 'updates to current milestone' do
- expect(find('.js-milestone-select .dropdown-toggle-text')).to have_content(milestone.title)
+ expect_filtered_search_input(search_query)
end
it 'does not change when closed link is clicked' do
find('.issues-state-filters a', text: "Closed").click
- expect(find('.js-milestone-select .dropdown-toggle-text')).to have_content(milestone.title)
+ expect_filtered_search_input(search_query)
end
it 'does not change when all link is clicked' do
find('.issues-state-filters a', text: "All").click
- expect(find('.js-milestone-select .dropdown-toggle-text')).to have_content(milestone.title)
+ expect_filtered_search_input(search_query)
end
end
end
describe 'for label from mr#index', js: true do
- before do
- visit namespace_project_merge_requests_path(project.namespace, project)
- find('.js-label-select').click
- wait_for_ajax
- end
-
- it 'filters by any label' do
- find('.dropdown-menu-labels a', text: 'Any Label').click
- page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
- wait_for_ajax
-
- expect(find('.labels-filter')).to have_content 'Label'
- end
-
it 'filters by no label' do
- find('.dropdown-menu-labels a', text: 'No Label').click
- page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
- wait_for_ajax
+ input_filtered_search('label:none')
- page.within '.labels-filter' do
- expect(page).to have_content 'Labels'
- end
- expect(find('.js-label-select .dropdown-toggle-text')).to have_content('Labels')
+ expect_mr_list_count(1)
+ expect_filtered_search_input('label:none')
end
it 'filters by a label' do
- find('.dropdown-menu-labels a', text: label.title).click
- page.within '.labels-filter' do
- expect(page).to have_content label.title
- end
- expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title)
+ input_filtered_search("label:~#{label.title}")
+
+ expect_mr_list_count(0)
+ expect_filtered_search_input("label:~#{label.title}")
end
it "filters by `won't fix` and another label" do
- page.within '.labels-filter' do
- click_link wontfix.title
- expect(page).to have_content wontfix.title
- click_link label.title
- end
+ input_filtered_search("label:~\"#{wontfix.title}\" label:~#{label.title}")
- expect(find('.js-label-select .dropdown-toggle-text')).to have_content("#{wontfix.title} +1 more")
+ expect_mr_list_count(0)
+ expect_filtered_search_input("label:~\"#{wontfix.title}\" label:~#{label.title}")
end
it "filters by `won't fix` label followed by another label after page load" do
- page.within '.labels-filter' do
- click_link wontfix.title
- expect(page).to have_content wontfix.title
- end
-
- find('body').click
-
- expect(find('.filtered-labels')).to have_content(wontfix.title)
-
- find('.js-label-select').click
- wait_for_ajax
- find('.dropdown-menu-labels a', text: label.title).click
-
- find('body').click
+ input_filtered_search("label:~\"#{wontfix.title}\"")
- expect(find('.filtered-labels')).to have_content(wontfix.title)
- expect(find('.filtered-labels')).to have_content(label.title)
+ expect_mr_list_count(0)
+ expect_filtered_search_input("label:~\"#{wontfix.title}\"")
- find('.js-label-select').click
- wait_for_ajax
+ input_filtered_search_keys(" label:~#{label.title}")
- expect(find('.dropdown-menu-labels li', text: wontfix.title)).to have_css('.is-active')
- expect(find('.dropdown-menu-labels li', text: label.title)).to have_css('.is-active')
- end
+ expect_filtered_search_input("label:~\"#{wontfix.title}\" label:~#{label.title}")
- it "selects and unselects `won't fix`" do
- find('.dropdown-menu-labels a', text: wontfix.title).click
- find('.dropdown-menu-labels a', text: wontfix.title).click
- # Close label dropdown to load
- find('body').click
- expect(page).not_to have_css('.filtered-labels')
+ expect_mr_list_count(0)
+ expect_filtered_search_input("label:~\"#{wontfix.title}\" label:~#{label.title}")
end
end
describe 'for assignee and label from issues#index' do
- before do
- visit namespace_project_merge_requests_path(project.namespace, project)
-
- find('.js-assignee-search').click
+ let(:search_query) { "assignee:@#{user.username} label:~#{label.title}" }
- find('.dropdown-menu-user-link', text: user.username).click
+ before do
+ input_filtered_search("assignee:@#{user.username}")
- expect(page).not_to have_selector('.mr-list .merge-request')
+ expect_mr_list_count(1)
+ expect_filtered_search_input("assignee:@#{user.username}")
- find('.js-label-select').click
+ input_filtered_search_keys(" label:~#{label.title}")
- find('.dropdown-menu-labels .dropdown-content a', text: label.title).click
- page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
+ expect_mr_list_count(1)
- wait_for_ajax
+ find("#state-opened[href=\"#{URI.parse(current_url).path}?assignee_username=#{user.username}&label_name%5B%5D=#{label.title}&scope=all&state=opened\"]")
end
context 'assignee and label', js: true do
it 'updates to current assignee and label' do
- expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name)
- expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title)
+ expect_filtered_search_input(search_query)
end
it 'does not change when closed link is clicked' do
find('.issues-state-filters a', text: "Closed").click
- expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name)
- expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title)
+ expect_filtered_search_input(search_query)
end
it 'does not change when all link is clicked' do
find('.issues-state-filters a', text: "All").click
- expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name)
- expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title)
+ expect_filtered_search_input(search_query)
end
end
end
@@ -203,11 +156,11 @@ describe 'Filter merge requests', feature: true do
bug_label = create(:label, project: project, title: 'bug')
milestone = create(:milestone, title: "8", project: project)
- mr = create(:merge_request,
- title: "Bug 2",
- source_project: project,
- target_project: project,
- source_branch: "bug2",
+ mr = create(:merge_request,
+ title: "Bug 2",
+ source_project: project,
+ target_project: project,
+ source_branch: "bug2",
milestone: milestone,
author: user,
assignee: user)
@@ -218,15 +171,13 @@ describe 'Filter merge requests', feature: true do
context 'only text', js: true do
it 'filters merge requests by searched text' do
- fill_in 'issuable_search', with: 'Bug'
+ input_filtered_search('bug')
- page.within '.mr-list' do
- expect(page).to have_selector('.merge-request', count: 2)
- end
+ expect_mr_list_count(2)
end
it 'does not show any merge requests' do
- fill_in 'issuable_search', with: 'testing'
+ input_filtered_search('testing')
page.within '.mr-list' do
expect(page).not_to have_selector('.merge-request')
@@ -234,82 +185,49 @@ describe 'Filter merge requests', feature: true do
end
end
- context 'text and dropdown options', js: true do
+ context 'filters and searches', js: true do
it 'filters by text and label' do
- fill_in 'issuable_search', with: 'Bug'
+ input_filtered_search('Bug')
- expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2)
- page.within '.mr-list' do
- expect(page).to have_selector('.merge-request', count: 2)
- end
+ expect_mr_list_count(2)
+ expect_filtered_search_input('Bug')
- click_button 'Label'
- page.within '.labels-filter' do
- click_link 'bug'
- end
- find('.dropdown-menu-close-icon').click
+ input_filtered_search_keys(' label:~bug')
- expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
- page.within '.mr-list' do
- expect(page).to have_selector('.merge-request', count: 1)
- end
+ expect_mr_list_count(1)
end
it 'filters by text and milestone' do
- fill_in 'issuable_search', with: 'Bug'
+ input_filtered_search('Bug')
- expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2)
- page.within '.mr-list' do
- expect(page).to have_selector('.merge-request', count: 2)
- end
+ expect_mr_list_count(2)
+ expect_filtered_search_input('Bug')
- click_button 'Milestone'
- page.within '.milestone-filter' do
- click_link '8'
- end
+ input_filtered_search_keys(' milestone:%8')
- expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
- page.within '.mr-list' do
- expect(page).to have_selector('.merge-request', count: 1)
- end
+ expect_mr_list_count(1)
end
it 'filters by text and assignee' do
- fill_in 'issuable_search', with: 'Bug'
+ input_filtered_search('Bug')
- expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2)
- page.within '.mr-list' do
- expect(page).to have_selector('.merge-request', count: 2)
- end
+ expect_mr_list_count(2)
+ expect_filtered_search_input('Bug')
- click_button 'Assignee'
- page.within '.dropdown-menu-assignee' do
- click_link user.name
- end
+ input_filtered_search_keys(" assignee:@#{user.username}")
- expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
- page.within '.mr-list' do
- expect(page).to have_selector('.merge-request', count: 1)
- end
+ expect_mr_list_count(1)
end
it 'filters by text and author' do
- fill_in 'issuable_search', with: 'Bug'
+ input_filtered_search('Bug')
- expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2)
- page.within '.mr-list' do
- expect(page).to have_selector('.merge-request', count: 2)
- end
+ expect_mr_list_count(2)
+ expect_filtered_search_input('Bug')
- click_button 'Author'
- page.within '.dropdown-menu-author' do
- click_link user.name
- end
+ input_filtered_search_keys(" author:@#{user.username}")
- expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
- page.within '.mr-list' do
- expect(page).to have_selector('.merge-request', count: 1)
- end
+ expect_mr_list_count(1)
end
end
end
@@ -328,18 +246,9 @@ describe 'Filter merge requests', feature: true do
end
it 'is able to filter and sort merge requests' do
- click_button 'Label'
- wait_for_ajax
- page.within '.labels-filter' do
- click_link 'bug'
- end
- find('.dropdown-menu-close-icon').click
- wait_for_ajax
+ input_filtered_search('label:~bug')
- expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2)
- page.within '.mr-list' do
- expect(page).to have_selector('.merge-request', count: 2)
- end
+ expect_mr_list_count(2)
click_button 'Last created'
page.within '.dropdown-menu-sort' do
@@ -352,4 +261,38 @@ describe 'Filter merge requests', feature: true do
end
end
end
+
+ describe 'filter by assignee id', js: true do
+ it 'filter by current user' do
+ visit namespace_project_merge_requests_path(project.namespace, project, assignee_id: user.id)
+
+ expect_filtered_search_input("assignee:@#{user.username}")
+ end
+
+ it 'filter by new user' do
+ new_user = create(:user)
+ project.add_developer(new_user)
+
+ visit namespace_project_merge_requests_path(project.namespace, project, assignee_id: new_user.id)
+
+ expect_filtered_search_input("assignee:@#{new_user.username}")
+ end
+ end
+
+ describe 'filter by author id', js: true do
+ it 'filter by current user' do
+ visit namespace_project_merge_requests_path(project.namespace, project, author_id: user.id)
+
+ expect_filtered_search_input("author:@#{user.username}")
+ end
+
+ it 'filter by new user' do
+ new_user = create(:user)
+ project.add_developer(new_user)
+
+ visit namespace_project_merge_requests_path(project.namespace, project, author_id: new_user.id)
+
+ expect_filtered_search_input("author:@#{new_user.username}")
+ end
+ end
end
diff --git a/spec/features/merge_requests/mini_pipeline_graph_spec.rb b/spec/features/merge_requests/mini_pipeline_graph_spec.rb
index b08bd36bde9..84ad8765d8f 100644
--- a/spec/features/merge_requests/mini_pipeline_graph_spec.rb
+++ b/spec/features/merge_requests/mini_pipeline_graph_spec.rb
@@ -66,7 +66,7 @@ feature 'Mini Pipeline Graph', :js, :feature do
end
it 'should close when toggle is clicked again' do
- toggle.click
+ toggle.trigger('click')
expect(toggle.find(:xpath, '..')).not_to have_selector('.mini-pipeline-graph-dropdown-menu')
end
diff --git a/spec/features/merge_requests/reset_filters_spec.rb b/spec/features/merge_requests/reset_filters_spec.rb
index 3a7ece7e1d6..58f11499e3f 100644
--- a/spec/features/merge_requests/reset_filters_spec.rb
+++ b/spec/features/merge_requests/reset_filters_spec.rb
@@ -1,17 +1,20 @@
require 'rails_helper'
feature 'Issues filter reset button', feature: true, js: true do
+ include FilteredSearchHelpers
+ include MergeRequestHelpers
include WaitForAjax
include IssueHelpers
- let!(:project) { create(:project, :public) }
- let!(:user) { create(:user)}
- let!(:milestone) { create(:milestone, project: project) }
- let!(:bug) { create(:label, project: project, name: 'bug')}
+ let!(:project) { create(:project, :public) }
+ let!(:user) { create(:user) }
+ let!(:milestone) { create(:milestone, project: project) }
+ let!(:bug) { create(:label, project: project, name: 'bug')}
let!(:mr1) { create(:merge_request, title: "Feature", source_project: project, target_project: project, source_branch: "Feature", milestone: milestone, author: user, assignee: user) }
let!(:mr2) { create(:merge_request, title: "Bugfix1", source_project: project, target_project: project, source_branch: "Bugfix1") }
- let(:merge_request_css) { '.merge-request' }
+ let(:merge_request_css) { '.merge-request' }
+ let(:clear_search_css) { '.filtered-search-input-container .clear-search' }
before do
mr2.labels << bug
@@ -50,7 +53,7 @@ feature 'Issues filter reset button', feature: true, js: true do
context 'when author filter has been applied' do
it 'resets the author filter' do
- visit_merge_requests(project, author_id: user.id)
+ visit_merge_requests(project, author_username: user.username)
expect(page).to have_css(merge_request_css, count: 1)
reset_filters
@@ -60,7 +63,7 @@ feature 'Issues filter reset button', feature: true, js: true do
context 'when assignee filter has been applied' do
it 'resets the assignee filter' do
- visit_merge_requests(project, assignee_id: user.id)
+ visit_merge_requests(project, assignee_username: user.username)
expect(page).to have_css(merge_request_css, count: 1)
reset_filters
@@ -70,7 +73,7 @@ feature 'Issues filter reset button', feature: true, js: true do
context 'when all filters have been applied' do
it 'resets all filters' do
- visit_merge_requests(project, assignee_id: user.id, author_id: user.id, milestone_title: milestone.title, label_name: bug.name, search: 'Bug')
+ visit_merge_requests(project, assignee_username: user.username, author_username: user.username, milestone_title: milestone.title, label_name: bug.name, search: 'Bug')
expect(page).to have_css(merge_request_css, count: 0)
reset_filters
@@ -82,15 +85,7 @@ feature 'Issues filter reset button', feature: true, js: true do
it 'the reset link should not be visible' do
visit_merge_requests(project)
expect(page).to have_css(merge_request_css, count: 2)
- expect(page).not_to have_css '.reset_filters'
+ expect(page).not_to have_css(clear_search_css)
end
end
-
- def visit_merge_requests(project, opts = {})
- visit namespace_project_merge_requests_path project.namespace, project, opts
- end
-
- def reset_filters
- find('.reset-filters').click
- end
end
diff --git a/spec/features/merge_requests/widget_spec.rb b/spec/features/merge_requests/widget_spec.rb
index fb3a1ae4bd0..4ad944366c8 100644
--- a/spec/features/merge_requests/widget_spec.rb
+++ b/spec/features/merge_requests/widget_spec.rb
@@ -29,8 +29,6 @@ describe 'Merge request', :feature, :js do
it 'shows widget status after creating new merge request' do
click_button 'Submit merge request'
- expect(find('.mr-state-widget')).to have_content('Checking ability to merge automatically')
-
wait_for_ajax
expect(page).to have_selector('.accept_merge_request')
@@ -54,4 +52,19 @@ describe 'Merge request', :feature, :js do
end
end
end
+
+ context 'merge error' do
+ before do
+ allow_any_instance_of(Repository).to receive(:merge).and_return(false)
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ click_button 'Accept Merge Request'
+ wait_for_ajax
+ end
+
+ it 'updates the MR widget' do
+ page.within('.mr-widget-body') do
+ expect(page).to have_content('Conflicts detected during merge')
+ end
+ end
+ end
end
diff --git a/spec/features/milestones/milestones_spec.rb b/spec/features/milestones/milestones_spec.rb
index aadd72a9f8e..8de9942c54e 100644
--- a/spec/features/milestones/milestones_spec.rb
+++ b/spec/features/milestones/milestones_spec.rb
@@ -2,6 +2,7 @@ require 'rails_helper'
describe 'Milestone draggable', feature: true, js: true do
include WaitForAjax
+ include DragTo
let(:milestone) { create(:milestone, project: project, title: 8.14) }
let(:project) { create(:empty_project, :public) }
@@ -75,7 +76,7 @@ describe 'Milestone draggable', feature: true, js: true do
create(:issue, params.merge(title: 'Foo', project: project, milestone: milestone))
visit namespace_project_milestone_path(project.namespace, project, milestone)
- issue.drag_to(issue_target)
+ drag_to(selector: '.issues-sortable-list', list_to_index: 1)
wait_for_ajax
end
@@ -85,7 +86,7 @@ describe 'Milestone draggable', feature: true, js: true do
visit namespace_project_milestone_path(project.namespace, project, milestone)
page.find("a[href='#tab-merge-requests']").click
- merge_request.drag_to(merge_request_target)
+ drag_to(selector: '.merge_requests-sortable-list', list_to_index: 1)
wait_for_ajax
end
diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb
index 7a562b5e03d..406d7cf791c 100644
--- a/spec/features/profile_spec.rb
+++ b/spec/features/profile_spec.rb
@@ -4,7 +4,7 @@ describe 'Profile account page', feature: true do
let(:user) { create(:user) }
before do
- login_as :user
+ login_as(user)
end
describe 'when signup is enabled' do
@@ -16,7 +16,7 @@ describe 'Profile account page', feature: true do
it { expect(page).to have_content('Remove account') }
it 'deletes the account' do
- expect { click_link 'Delete account' }.to change { User.count }.by(-1)
+ expect { click_link 'Delete account' }.to change { User.where(id: user.id).count }.by(-1)
expect(current_path).to eq(new_user_session_path)
end
end
diff --git a/spec/features/profiles/personal_access_tokens_spec.rb b/spec/features/profiles/personal_access_tokens_spec.rb
index 55a01057c83..eb7b8a24669 100644
--- a/spec/features/profiles/personal_access_tokens_spec.rb
+++ b/spec/features/profiles/personal_access_tokens_spec.rb
@@ -34,7 +34,7 @@ describe 'Profile > Personal Access Tokens', feature: true, js: true do
# Set date to 1st of next month
find_field("Expires at").trigger('focus')
- find("a[title='Next']").click
+ find(".pika-next").click
click_on "1"
# Scopes
diff --git a/spec/features/profiles/preferences_spec.rb b/spec/features/profiles/preferences_spec.rb
index a6b841c0210..15c8677fcd3 100644
--- a/spec/features/profiles/preferences_spec.rb
+++ b/spec/features/profiles/preferences_spec.rb
@@ -8,35 +8,6 @@ describe 'Profile > Preferences', feature: true do
visit profile_preferences_path
end
- describe 'User changes their application theme', js: true do
- let(:default) { Gitlab::Themes.default }
- let(:theme) { Gitlab::Themes.by_id(5) }
-
- it 'creates a flash message' do
- choose "user_theme_id_#{theme.id}"
-
- expect_preferences_saved_message
- end
-
- it 'updates their preference' do
- choose "user_theme_id_#{theme.id}"
-
- allowing_for_delay do
- visit page.current_path
- expect(page).to have_checked_field("user_theme_id_#{theme.id}")
- end
- end
-
- it 'reflects the changes immediately' do
- expect(page).to have_selector("body.#{default.css_class}")
-
- choose "user_theme_id_#{theme.id}"
-
- expect(page).not_to have_selector("body.#{default.css_class}")
- expect(page).to have_selector("body.#{theme.css_class}")
- end
- end
-
describe 'User changes their syntax highlighting theme', js: true do
it 'creates a flash message' do
choose 'user_color_scheme_id_5'
diff --git a/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb b/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb
new file mode 100644
index 00000000000..e05fbb3715c
--- /dev/null
+++ b/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+feature 'Profile > Notifications > User changes notified_of_own_activity setting', feature: true, js: true do
+ let(:user) { create(:user) }
+
+ before do
+ login_as(user)
+ end
+
+ scenario 'User opts into receiving notifications about their own activity' do
+ visit profile_notifications_path
+
+ expect(page).not_to have_checked_field('user[notified_of_own_activity]')
+
+ check 'user[notified_of_own_activity]'
+
+ expect(page).to have_content('Notification settings saved')
+ expect(page).to have_checked_field('user[notified_of_own_activity]')
+ end
+
+ scenario 'User opts out of receiving notifications about their own activity' do
+ user.update!(notified_of_own_activity: true)
+ visit profile_notifications_path
+
+ expect(page).to have_checked_field('user[notified_of_own_activity]')
+
+ uncheck 'user[notified_of_own_activity]'
+
+ expect(page).to have_content('Notification settings saved')
+ expect(page).not_to have_checked_field('user[notified_of_own_activity]')
+ end
+end
diff --git a/spec/features/projects/badges/list_spec.rb b/spec/features/projects/badges/list_spec.rb
index 67a4a5d1ab1..ae9db0c0d6e 100644
--- a/spec/features/projects/badges/list_spec.rb
+++ b/spec/features/projects/badges/list_spec.rb
@@ -14,7 +14,8 @@ feature 'list of badges' do
expect(page).to have_content 'build status'
expect(page).to have_content 'Markdown'
expect(page).to have_content 'HTML'
- expect(page).to have_css('.highlight', count: 2)
+ expect(page).to have_content 'AsciiDoc'
+ expect(page).to have_css('.highlight', count: 3)
expect(page).to have_xpath("//img[@alt='build status']")
page.within('.highlight', match: :first) do
@@ -28,7 +29,8 @@ feature 'list of badges' do
expect(page).to have_content 'coverage report'
expect(page).to have_content 'Markdown'
expect(page).to have_content 'HTML'
- expect(page).to have_css('.highlight', count: 2)
+ expect(page).to have_content 'AsciiDoc'
+ expect(page).to have_css('.highlight', count: 3)
expect(page).to have_xpath("//img[@alt='coverage report']")
page.within('.highlight', match: :first) do
diff --git a/spec/features/projects/builds_spec.rb b/spec/features/projects/builds_spec.rb
index f7e0115643e..2116721b224 100644
--- a/spec/features/projects/builds_spec.rb
+++ b/spec/features/projects/builds_spec.rb
@@ -109,6 +109,10 @@ feature 'Builds', :feature do
expect(page).to have_content pipeline.git_commit_message
expect(page).to have_content pipeline.git_author_name
end
+
+ it 'shows active build' do
+ expect(page).to have_selector('.build-job.active')
+ end
end
context "Job from other project" do
@@ -271,7 +275,7 @@ feature 'Builds', :feature do
let!(:deployment) { create(:deployment, environment: environment, sha: project.commit.id) }
let(:build) { create(:ci_build, :success, environment: environment.name, pipeline: pipeline) }
- it 'shows a link to lastest deployment' do
+ it 'shows a link to latest deployment' do
visit namespace_project_build_path(project.namespace, project, build)
expect(page).to have_link('latest deployment')
diff --git a/spec/features/projects/files/find_file_keyboard_spec.rb b/spec/features/projects/files/find_file_keyboard_spec.rb
index fc88fd74af8..582349d8d5b 100644
--- a/spec/features/projects/files/find_file_keyboard_spec.rb
+++ b/spec/features/projects/files/find_file_keyboard_spec.rb
@@ -22,7 +22,7 @@ feature 'Find file keyboard shortcuts', feature: true, js: true do
expect(page).to have_selector('.blob-content-holder')
- page.within('.file-title') do
+ page.within('.js-file-title') do
expect(page).to have_content('CHANGELOG')
end
end
@@ -35,7 +35,7 @@ feature 'Find file keyboard shortcuts', feature: true, js: true do
expect(page).to have_selector('.blob-content-holder')
- page.within('.file-title') do
+ page.within('.js-file-title') do
expect(page).to have_content('application.js')
end
end
diff --git a/spec/features/projects/files/project_owner_creates_license_file_spec.rb b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
index 64094af29c0..f8ef4577a26 100644
--- a/spec/features/projects/files/project_owner_creates_license_file_spec.rb
+++ b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
@@ -25,7 +25,7 @@ feature 'project owner creates a license file', feature: true, js: true do
select_template('MIT License')
file_content = first('.file-editor')
- expect(file_content).to have_content('The MIT License (MIT)')
+ expect(file_content).to have_content('MIT License')
expect(file_content).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
fill_in :commit_message, with: 'Add a LICENSE file', visible: true
@@ -33,7 +33,7 @@ feature 'project owner creates a license file', feature: true, js: true do
expect(current_path).to eq(
namespace_project_blob_path(project.namespace, project, 'master/LICENSE'))
- expect(page).to have_content('The MIT License (MIT)')
+ expect(page).to have_content('MIT License')
expect(page).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
end
@@ -49,7 +49,7 @@ feature 'project owner creates a license file', feature: true, js: true do
select_template('MIT License')
file_content = first('.file-editor')
- expect(file_content).to have_content('The MIT License (MIT)')
+ expect(file_content).to have_content('MIT License')
expect(file_content).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
fill_in :commit_message, with: 'Add a LICENSE file', visible: true
@@ -57,7 +57,7 @@ feature 'project owner creates a license file', feature: true, js: true do
expect(current_path).to eq(
namespace_project_blob_path(project.namespace, project, 'master/LICENSE'))
- expect(page).to have_content('The MIT License (MIT)')
+ expect(page).to have_content('MIT License')
expect(page).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
end
diff --git a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
index 4453b6d485f..420db962318 100644
--- a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
+++ b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
@@ -24,7 +24,7 @@ feature 'project owner sees a link to create a license file in empty project', f
select_template('MIT License')
file_content = first('.file-editor')
- expect(file_content).to have_content('The MIT License (MIT)')
+ expect(file_content).to have_content('MIT License')
expect(file_content).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
fill_in :commit_message, with: 'Add a LICENSE file', visible: true
@@ -34,7 +34,7 @@ feature 'project owner sees a link to create a license file in empty project', f
expect(current_path).to eq(
namespace_project_blob_path(project.namespace, project, 'master/LICENSE'))
- expect(page).to have_content('The MIT License (MIT)')
+ expect(page).to have_content('MIT License')
expect(page).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
end
diff --git a/spec/features/projects/import_export/export_file_spec.rb b/spec/features/projects/import_export/export_file_spec.rb
index 16dddb2a86b..40caf89dd54 100644
--- a/spec/features/projects/import_export/export_file_spec.rb
+++ b/spec/features/projects/import_export/export_file_spec.rb
@@ -9,7 +9,7 @@ feature 'Import/Export - project export integration test', feature: true, js: tr
include ExportFileHelper
let(:user) { create(:admin) }
- let(:export_path) { "#{Dir::tmpdir}/import_file_spec" }
+ let(:export_path) { "#{Dir.tmpdir}/import_file_spec" }
let(:config_hash) { YAML.load_file(Gitlab::ImportExport.config_file).deep_stringify_keys }
let(:sensitive_words) { %w[pass secret token key] }
diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb
index 3015576f6f8..2d1106ea3e8 100644
--- a/spec/features/projects/import_export/import_file_spec.rb
+++ b/spec/features/projects/import_export/import_file_spec.rb
@@ -4,7 +4,7 @@ feature 'Import/Export - project import integration test', feature: true, js: tr
include Select2Helper
let(:file) { File.join(Rails.root, 'spec', 'features', 'projects', 'import_export', 'test_project_export.tar.gz') }
- let(:export_path) { "#{Dir::tmpdir}/import_file_spec" }
+ let(:export_path) { "#{Dir.tmpdir}/import_file_spec" }
background do
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
diff --git a/spec/features/projects/import_export/namespace_export_file_spec.rb b/spec/features/projects/import_export/namespace_export_file_spec.rb
index d0bafc6168c..cb399ea55df 100644
--- a/spec/features/projects/import_export/namespace_export_file_spec.rb
+++ b/spec/features/projects/import_export/namespace_export_file_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
feature 'Import/Export - Namespace export file cleanup', feature: true, js: true do
- let(:export_path) { "#{Dir::tmpdir}/import_file_spec" }
+ let(:export_path) { "#{Dir.tmpdir}/import_file_spec" }
let(:config_hash) { YAML.load_file(Gitlab::ImportExport.config_file).deep_stringify_keys }
let(:project) { create(:empty_project) }
diff --git a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
index 81b0c991d4f..7414ce21f59 100644
--- a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
+++ b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
@@ -37,7 +37,7 @@ feature 'Issue prioritization', feature: true do
page.within('.issues-holder') do
issue_titles = all('.issues-list .issue-title-text').map(&:text)
- expect(issue_titles).to eq(['issue_4', 'issue_3', 'issue_5', 'issue_2', 'issue_1'])
+ expect(issue_titles).to eq(%w(issue_4 issue_3 issue_5 issue_2 issue_1))
end
end
end
@@ -77,7 +77,7 @@ feature 'Issue prioritization', feature: true do
expect(issue_titles[0..1]).to contain_exactly('issue_5', 'issue_8')
expect(issue_titles[2..4]).to contain_exactly('issue_1', 'issue_3', 'issue_7')
- expect(issue_titles[5..-1]).to eq(['issue_2', 'issue_4', 'issue_6'])
+ expect(issue_titles[5..-1]).to eq(%w(issue_2 issue_4 issue_6))
end
end
end
diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb
index 97ce9cdfd87..1e900d7e660 100644
--- a/spec/features/projects/labels/update_prioritization_spec.rb
+++ b/spec/features/projects/labels/update_prioritization_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
feature 'Prioritize labels', feature: true do
include WaitForAjax
+ include DragTo
let(:user) { create(:user) }
let(:group) { create(:group) }
@@ -99,7 +100,7 @@ feature 'Prioritize labels', feature: true do
expect(page).to have_content 'wontfix'
# Sort labels
- find("#project_label_#{bug.id}").drag_to find("#group_label_#{feature.id}")
+ drag_to(selector: '.js-prioritized-labels', from_index: 1, to_index: 2)
page.within('.prioritized-labels') do
expect(first('li')).to have_content('feature')
diff --git a/spec/features/projects/main/download_buttons_spec.rb b/spec/features/projects/main/download_buttons_spec.rb
index 227ccf9459c..02198ff3e41 100644
--- a/spec/features/projects/main/download_buttons_spec.rb
+++ b/spec/features/projects/main/download_buttons_spec.rb
@@ -39,6 +39,13 @@ feature 'Download buttons in project main page', feature: true do
expect(page).to have_link "Download '#{build.name}'", href: href
end
+
+ scenario 'download links have download attribute' do
+ expect(page).to have_selector('a', text: 'Download')
+ page.all('a', text: 'Download').each do |link|
+ expect(link[:download]).to eq ''
+ end
+ end
end
end
end
diff --git a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
index f136d9ce0fa..c3f45be6e4b 100644
--- a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
+++ b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
@@ -14,15 +14,16 @@ feature 'Projects > Members > Master adds member with expiration date', feature:
login_as(master)
end
- scenario 'expiration date is displayed in the members list', js: true do
+ scenario 'expiration date is displayed in the members list' do
travel_to Time.zone.parse('2016-08-06 08:00') do
- visit namespace_project_settings_members_path(project.namespace, project)
+ date = 4.days.from_now
+ visit namespace_project_project_members_path(project.namespace, project)
+
page.within '.users-project-form' do
select2(new_member.id, from: '#user_ids', multiple: true)
- fill_in 'expires_at', with: '2016-08-10'
+ fill_in 'expires_at', with: date.to_s(:medium)
+ click_on 'Add to project'
end
- find('.users-project-form').click
- click_on 'Add to project'
page.within "#project_member_#{new_member.project_members.first.id}" do
expect(page).to have_content('Expires in 4 days')
@@ -32,11 +33,12 @@ feature 'Projects > Members > Master adds member with expiration date', feature:
scenario 'change expiration date' do
travel_to Time.zone.parse('2016-08-06 08:00') do
- project.team.add_users([new_member.id], :developer, expires_at: '2016-09-06')
+ date = 3.days.from_now
+ project.team.add_users([new_member.id], :developer, expires_at: Date.today.to_s(:medium))
visit namespace_project_project_members_path(project.namespace, project)
page.within "#project_member_#{new_member.project_members.first.id}" do
- find('.js-access-expiration-date').set '2016-08-09'
+ find('.js-access-expiration-date').set date.to_s(:medium)
wait_for_ajax
expect(page).to have_content('Expires in 3 days')
end
diff --git a/spec/features/projects/members/sorting_spec.rb b/spec/features/projects/members/sorting_spec.rb
index d6ebb523f95..c7a32a65e49 100644
--- a/spec/features/projects/members/sorting_spec.rb
+++ b/spec/features/projects/members/sorting_spec.rb
@@ -85,7 +85,7 @@ feature 'Projects > Members > Sorting', feature: true do
end
def visit_members_list(sort:)
- visit namespace_project_project_members_path(project.namespace.to_param, project.to_param, sort: sort)
+ visit namespace_project_project_members_path(project.namespace.to_param, project, sort: sort)
end
def first_member
diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb
index b56e562b2b6..45185f2dd1f 100644
--- a/spec/features/projects/new_project_spec.rb
+++ b/spec/features/projects/new_project_spec.rb
@@ -19,6 +19,51 @@ feature "New project", feature: true do
end
end
+ context "Namespace selector" do
+ context "with user namespace" do
+ before do
+ visit new_project_path
+ end
+
+ it "selects the user namespace" do
+ namespace = find("#project_namespace_id")
+
+ expect(namespace.text).to eq user.username
+ end
+ end
+
+ context "with group namespace" do
+ let(:group) { create(:group, :private, owner: user) }
+
+ before do
+ group.add_owner(user)
+ visit new_project_path(namespace_id: group.id)
+ end
+
+ it "selects the group namespace" do
+ namespace = find("#project_namespace_id option[selected]")
+
+ expect(namespace.text).to eq group.name
+ end
+
+ context "on validation error" do
+ before do
+ fill_in('project_path', with: 'private-group-project')
+ choose('Internal')
+ click_button('Create project')
+
+ expect(page).to have_css '.project-edit-errors .alert.alert-danger'
+ end
+
+ it "selects the group namespace" do
+ namespace = find("#project_namespace_id option[selected]")
+
+ expect(namespace.text).to eq group.name
+ end
+ end
+ end
+ end
+
context 'Import project options' do
before do
visit new_project_path
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index 0b5ccc8c515..9f06e52ab55 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -54,7 +54,7 @@ describe 'Pipeline', :feature, :js do
expect(page).to have_content('Build')
expect(page).to have_content('Test')
expect(page).to have_content('Deploy')
- expect(page).to have_content('Retry failed')
+ expect(page).to have_content('Retry')
expect(page).to have_content('Cancel running')
end
@@ -164,9 +164,9 @@ describe 'Pipeline', :feature, :js do
it { expect(page).not_to have_content('retried') }
context 'when retrying' do
- before { click_on 'Retry failed' }
+ before { find('.js-retry-button').trigger('click') }
- it { expect(page).not_to have_content('Retry failed') }
+ it { expect(page).not_to have_content('Retry') }
end
end
@@ -198,7 +198,7 @@ describe 'Pipeline', :feature, :js do
expect(page).to have_content(build_failed.id)
expect(page).to have_content(build_running.id)
expect(page).to have_content(build_external.id)
- expect(page).to have_content('Retry failed')
+ expect(page).to have_content('Retry')
expect(page).to have_content('Cancel running')
expect(page).to have_link('Play')
end
@@ -226,9 +226,9 @@ describe 'Pipeline', :feature, :js do
it { expect(page).not_to have_content('retried') }
context 'when retrying' do
- before { click_on 'Retry failed' }
+ before { find('.js-retry-button').trigger('click') }
- it { expect(page).not_to have_content('Retry failed') }
+ it { expect(page).not_to have_content('Retry') }
it { expect(page).to have_selector('.retried') }
end
end
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index ca18ac073d8..18ec31f3280 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -26,18 +26,66 @@ describe 'Pipelines', :feature, :js do
)
end
- [:all, :running, :branches].each do |scope|
- context "when displaying #{scope}" do
- before do
- visit_project_pipelines(scope: scope)
- end
+ context 'scope' do
+ before do
+ create(:ci_empty_pipeline, status: 'pending', project: project, sha: project.commit.id, ref: 'master')
+ create(:ci_empty_pipeline, status: 'running', project: project, sha: project.commit.id, ref: 'master')
+ create(:ci_empty_pipeline, status: 'created', project: project, sha: project.commit.id, ref: 'master')
+ create(:ci_empty_pipeline, status: 'success', project: project, sha: project.commit.id, ref: 'master')
+ end
+
+ [:all, :running, :pending, :finished, :branches].each do |scope|
+ context "when displaying #{scope}" do
+ before do
+ visit_project_pipelines(scope: scope)
+ end
- it 'contains pipeline commit short SHA' do
- expect(page).to have_content(pipeline.short_sha)
+ it 'contains pipeline commit short SHA' do
+ expect(page).to have_content(pipeline.short_sha)
+ end
+
+ it 'contains branch name' do
+ expect(page).to have_content(pipeline.ref)
+ end
end
end
end
+ context 'header tabs' do
+ before do
+ visit namespace_project_pipelines_path(project.namespace, project)
+ wait_for_vue_resource
+ end
+
+ it 'shows a tab for All pipelines and count' do
+ expect(page.find('.js-pipelines-tab-all a').text).to include('All')
+ expect(page.find('.js-pipelines-tab-all .badge').text).to include('1')
+ end
+
+ it 'shows a tab for Pending pipelines and count' do
+ expect(page.find('.js-pipelines-tab-pending a').text).to include('Pending')
+ expect(page.find('.js-pipelines-tab-pending .badge').text).to include('0')
+ end
+
+ it 'shows a tab for Running pipelines and count' do
+ expect(page.find('.js-pipelines-tab-running a').text).to include('Running')
+ expect(page.find('.js-pipelines-tab-running .badge').text).to include('1')
+ end
+
+ it 'shows a tab for Finished pipelines and count' do
+ expect(page.find('.js-pipelines-tab-finished a').text).to include('Finished')
+ expect(page.find('.js-pipelines-tab-finished .badge').text).to include('0')
+ end
+
+ it 'shows a tab for Branches' do
+ expect(page.find('.js-pipelines-tab-branches a').text).to include('Branches')
+ end
+
+ it 'shows a tab for Tags' do
+ expect(page.find('.js-pipelines-tab-tags a').text).to include('Tags')
+ end
+ end
+
context 'when pipeline is cancelable' do
let!(:build) do
create(:ci_build, pipeline: pipeline,
@@ -214,6 +262,14 @@ describe 'Pipelines', :feature, :js do
expect(page).to have_link(with_artifacts.name)
end
+
+ it 'has download attribute on download links' do
+ find('.js-pipeline-dropdown-download').click
+ expect(page).to have_selector('a', text: 'Download')
+ page.all('.build-artifacts a', text: 'Download').each do |link|
+ expect(link[:download]).to eq ''
+ end
+ end
end
context 'with artifacts expired' do
@@ -272,6 +328,39 @@ describe 'Pipelines', :feature, :js do
expect(build.reload).to be_canceled
end
end
+
+ context 'dropdown jobs list' do
+ it 'should keep the dropdown open when the user ctr/cmd + clicks in the job name' do
+ find('.js-builds-dropdown-button').trigger('click')
+
+ execute_script('var e = $.Event("keydown", { keyCode: 64 }); $("body").trigger(e);')
+
+ find('.mini-pipeline-graph-dropdown-item').trigger('click')
+
+ expect(page).to have_selector('.js-ci-action-icon')
+ end
+ end
+ end
+
+ context 'with pagination' do
+ before do
+ allow(Ci::Pipeline).to receive(:default_per_page).and_return(1)
+ create(:ci_empty_pipeline, project: project)
+ end
+
+ it 'should render pagination' do
+ visit namespace_project_pipelines_path(project.namespace, project)
+ wait_for_vue_resource
+
+ expect(page).to have_selector('.gl-pagination')
+ end
+
+ it 'should render second page of pipelines' do
+ visit namespace_project_pipelines_path(project.namespace, project, page: '2')
+ wait_for_vue_resource
+
+ expect(page).to have_selector('.gl-pagination .page', count: 2)
+ end
end
end
diff --git a/spec/features/projects/ref_switcher_spec.rb b/spec/features/projects/ref_switcher_spec.rb
index 38fe2d92885..3b8f0b2d3f8 100644
--- a/spec/features/projects/ref_switcher_spec.rb
+++ b/spec/features/projects/ref_switcher_spec.rb
@@ -20,9 +20,11 @@ feature 'Ref switcher', feature: true, js: true do
input.set 'binary'
wait_for_ajax
- input.native.send_keys :down
- input.native.send_keys :down
- input.native.send_keys :enter
+ expect(find('.dropdown-content ul')).to have_selector('li', count: 6)
+
+ page.within '.dropdown-content ul' do
+ input.native.send_keys :enter
+ end
end
expect(page).to have_title 'binary-encoding'
diff --git a/spec/features/protected_branches/access_control_ce_spec.rb b/spec/features/protected_branches/access_control_ce_spec.rb
index 395c61a4743..e4aca25a339 100644
--- a/spec/features/protected_branches/access_control_ce_spec.rb
+++ b/spec/features/protected_branches/access_control_ce_spec.rb
@@ -26,7 +26,11 @@ RSpec.shared_examples "protected branches > access control > CE" do
within(".protected-branches-list") do
find(".js-allowed-to-push").click
- within('.js-allowed-to-push-container') { click_on access_type_name }
+
+ within('.js-allowed-to-push-container') do
+ expect(first("li")).to have_content("Roles")
+ click_on access_type_name
+ end
end
wait_for_ajax
@@ -61,7 +65,11 @@ RSpec.shared_examples "protected branches > access control > CE" do
within(".protected-branches-list") do
find(".js-allowed-to-merge").click
- within('.js-allowed-to-merge-container') { click_on access_type_name }
+
+ within('.js-allowed-to-merge-container') do
+ expect(first("li")).to have_content("Roles")
+ click_on access_type_name
+ end
end
wait_for_ajax
diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb
index 0fe5a897565..7da05defa81 100644
--- a/spec/features/search_spec.rb
+++ b/spec/features/search_spec.rb
@@ -186,7 +186,7 @@ describe "Search", feature: true do
sleep 2
expect(page).to have_selector('.merge-requests-holder')
- expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name)
+ expect(find('.filtered-search').value).to eq("assignee:@#{user.username}")
end
it 'takes user to her MR page when MR authored is clicked' do
@@ -194,7 +194,7 @@ describe "Search", feature: true do
sleep 2
expect(page).to have_selector('.merge-requests-holder')
- expect(find('.js-author-search .dropdown-toggle-text')).to have_content(user.name)
+ expect(find('.filtered-search').value).to eq("author:@#{user.username}")
end
end
diff --git a/spec/features/snippets/user_snippets_spec.rb b/spec/features/snippets/user_snippets_spec.rb
new file mode 100644
index 00000000000..191c2fb9a22
--- /dev/null
+++ b/spec/features/snippets/user_snippets_spec.rb
@@ -0,0 +1,49 @@
+require 'rails_helper'
+
+feature 'User Snippets', feature: true do
+ let(:author) { create(:user) }
+ let!(:public_snippet) { create(:personal_snippet, :public, author: author, title: "This is a public snippet") }
+ let!(:internal_snippet) { create(:personal_snippet, :internal, author: author, title: "This is an internal snippet") }
+ let!(:private_snippet) { create(:personal_snippet, :private, author: author, title: "This is a private snippet") }
+
+ background do
+ login_as author
+ visit dashboard_snippets_path
+ end
+
+ scenario 'View all of my snippets' do
+ expect(page).to have_content(public_snippet.title)
+ expect(page).to have_content(internal_snippet.title)
+ expect(page).to have_content(private_snippet.title)
+ end
+
+ scenario 'View my public snippets' do
+ page.within('.snippet-scope-menu') do
+ click_link "Public"
+ end
+
+ expect(page).to have_content(public_snippet.title)
+ expect(page).not_to have_content(internal_snippet.title)
+ expect(page).not_to have_content(private_snippet.title)
+ end
+
+ scenario 'View my internal snippets' do
+ page.within('.snippet-scope-menu') do
+ click_link "Internal"
+ end
+
+ expect(page).not_to have_content(public_snippet.title)
+ expect(page).to have_content(internal_snippet.title)
+ expect(page).not_to have_content(private_snippet.title)
+ end
+
+ scenario 'View my private snippets' do
+ page.within('.snippet-scope-menu') do
+ click_link "Private"
+ end
+
+ expect(page).not_to have_content(public_snippet.title)
+ expect(page).not_to have_content(internal_snippet.title)
+ expect(page).to have_content(private_snippet.title)
+ end
+end
diff --git a/spec/features/todos/todos_filtering_spec.rb b/spec/features/todos/todos_filtering_spec.rb
index d1f2bc78884..e8f06916d53 100644
--- a/spec/features/todos/todos_filtering_spec.rb
+++ b/spec/features/todos/todos_filtering_spec.rb
@@ -98,15 +98,58 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
expect(find('.todos-list')).not_to have_content merge_request.to_reference
end
- it 'filters by action' do
- click_button 'Action'
- within '.dropdown-menu-action' do
- click_link 'Assigned'
+ describe 'filter by action' do
+ before do
+ create(:todo, :build_failed, user: user_1, author: user_2, project: project_1)
+ create(:todo, :marked, user: user_1, author: user_2, project: project_1, target: issue)
end
- wait_for_ajax
+ it 'filters by Assigned' do
+ filter_action('Assigned')
+
+ expect_to_see_action(:assigned)
+ end
+
+ it 'filters by Mentioned' do
+ filter_action('Mentioned')
+
+ expect_to_see_action(:mentioned)
+ end
+
+ it 'filters by Added' do
+ filter_action('Added')
+
+ expect_to_see_action(:marked)
+ end
+
+ it 'filters by Pipelines' do
+ filter_action('Pipelines')
- expect(find('.todos-list')).to have_content ' assigned you '
- expect(find('.todos-list')).not_to have_content ' mentioned '
+ expect_to_see_action(:build_failed)
+ end
+
+ def filter_action(name)
+ click_button 'Action'
+ within '.dropdown-menu-action' do
+ click_link name
+ end
+
+ wait_for_ajax
+ end
+
+ def expect_to_see_action(action_name)
+ action_names = {
+ assigned: ' assigned you ',
+ mentioned: ' mentioned ',
+ marked: ' added a todo for ',
+ build_failed: ' build failed for '
+ }
+
+ action_name_text = action_names.delete(action_name)
+ expect(find('.todos-list')).to have_content action_name_text
+ action_names.each_value do |other_action_text|
+ expect(find('.todos-list')).not_to have_content other_action_text
+ end
+ end
end
end
diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb
index 1b352be9331..3495091a0d5 100644
--- a/spec/features/todos/todos_spec.rb
+++ b/spec/features/todos/todos_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe 'Dashboard Todos', feature: true do
+ include WaitForAjax
+
let(:user) { create(:user) }
let(:author) { create(:user) }
let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
@@ -34,39 +36,64 @@ describe 'Dashboard Todos', feature: true do
end
end
- describe 'deleting the todo' do
+ shared_examples 'deleting the todo' do
before do
- first('.done-todo').click
+ first('.js-done-todo').click
+ end
+
+ it 'is marked as done-reversible in the list' do
+ expect(page).to have_selector('.todos-list .todo.todo-pending.done-reversible')
+ end
+
+ it 'shows Undo button' do
+ expect(page).to have_selector('.js-undo-todo', visible: true)
+ expect(page).to have_selector('.js-done-todo', visible: false)
end
- it 'is removed from the list' do
- expect(page).not_to have_selector('.todos-list .todo')
+ it 'updates todo count' do
+ expect(page).to have_content 'To do 0'
+ expect(page).to have_content 'Done 1'
end
- it 'shows "All done" message' do
- expect(page).to have_selector('.todos-all-done', count: 1)
+ it 'has not "All done" message' do
+ expect(page).not_to have_selector('.todos-all-done')
end
end
- context 'todo is stale on the page' do
+ shared_examples 'deleting and restoring the todo' do
before do
- todos = TodosFinder.new(user, state: :pending).execute
- TodoService.new.mark_todos_as_done(todos, user)
+ first('.js-done-todo').click
+ wait_for_ajax
+ first('.js-undo-todo').click
+ end
+
+ it 'is marked back as pending in the list' do
+ expect(page).not_to have_selector('.todos-list .todo.todo-pending.done-reversible')
+ expect(page).to have_selector('.todos-list .todo.todo-pending')
+ end
+
+ it 'shows Done button' do
+ expect(page).to have_selector('.js-undo-todo', visible: false)
+ expect(page).to have_selector('.js-done-todo', visible: true)
end
- describe 'deleting the todo' do
- before do
- first('.done-todo').click
- end
+ it 'updates todo count' do
+ expect(page).to have_content 'To do 1'
+ expect(page).to have_content 'Done 0'
+ end
+ end
- it 'is removed from the list' do
- expect(page).not_to have_selector('.todos-list .todo')
- end
+ it_behaves_like 'deleting the todo'
+ it_behaves_like 'deleting and restoring the todo'
- it 'shows "All done" message' do
- expect(page).to have_selector('.todos-all-done', count: 1)
- end
+ context 'todo is stale on the page' do
+ before do
+ todos = TodosFinder.new(user, state: :pending).execute
+ TodoService.new.mark_todos_as_done(todos, user)
end
+
+ it_behaves_like 'deleting the todo'
+ it_behaves_like 'deleting and restoring the todo'
end
end
@@ -113,18 +140,6 @@ describe 'Dashboard Todos', feature: true do
expect(page).to have_selector('.gl-pagination .page', count: 2)
end
- describe 'completing last todo from last page', js: true do
- it 'redirects to the previous page' do
- visit dashboard_todos_path(page: 2)
- expect(page).to have_css("#todo_#{Todo.last.id}")
-
- click_link('Done')
-
- expect(current_path).to eq dashboard_todos_path
- expect(page).to have_css("#todo_#{Todo.first.id}")
- end
- end
-
describe 'mark all as done', js: true do
before do
visit dashboard_todos_path
@@ -156,6 +171,29 @@ describe 'Dashboard Todos', feature: true do
end
end
+ context 'User have large number of todos' do
+ before do
+ create_list(:todo, 101, :mentioned, user: user, project: project, target: issue, author: author)
+
+ login_as(user)
+ visit dashboard_todos_path
+ end
+
+ it 'shows 99+ for count >= 100 in notification' do
+ expect(page).to have_selector('.todos-pending-count', text: '99+')
+ end
+
+ it 'shows exact number in To do tab' do
+ expect(page).to have_selector('.todos-pending .badge', text: '101')
+ end
+
+ it 'shows exact number for count < 100' do
+ 3.times { first('.js-done-todo').click }
+
+ expect(page).to have_selector('.todos-pending-count', text: '98')
+ end
+ end
+
context 'User has a Build Failed todo' do
let!(:todo) { create(:todo, :build_failed, user: user, project: project, author: author) }
diff --git a/spec/features/variables_spec.rb b/spec/features/variables_spec.rb
index 9a4bc027004..a362d6fd3b6 100644
--- a/spec/features/variables_spec.rb
+++ b/spec/features/variables_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe 'Project variables', js: true do
let(:user) { create(:user) }
let(:project) { create(:project) }
- let(:variable) { create(:ci_variable, key: 'test') }
+ let(:variable) { create(:ci_variable, key: 'test_key', value: 'test value') }
before do
login_as(user)
@@ -24,11 +24,23 @@ describe 'Project variables', js: true do
fill_in('variable_value', with: 'key value')
click_button('Add new variable')
+ expect(page).to have_content('Variables were successfully updated.')
page.within('.variables-table') do
expect(page).to have_content('key')
end
end
+ it 'adds empty variable' do
+ fill_in('variable_key', with: 'new_key')
+ fill_in('variable_value', with: '')
+ click_button('Add new variable')
+
+ expect(page).to have_content('Variables were successfully updated.')
+ page.within('.variables-table') do
+ expect(page).to have_content('new_key')
+ end
+ end
+
it 'reveals and hides new variable' do
fill_in('variable_key', with: 'key')
fill_in('variable_value', with: 'key value')
@@ -72,8 +84,20 @@ describe 'Project variables', js: true do
fill_in('variable_value', with: 'key value')
click_button('Save variable')
+ expect(page).to have_content('Variable was successfully updated.')
+ expect(project.variables.first.value).to eq('key value')
+ end
+
+ it 'edits variable with empty value' do
page.within('.variables-table') do
- expect(page).to have_content('key')
+ find('.btn-variable-edit').click
end
+
+ expect(page).to have_content('Update variable')
+ fill_in('variable_value', with: '')
+ click_button('Save variable')
+
+ expect(page).to have_content('Variable was successfully updated.')
+ expect(project.variables.first.value).to eq('')
end
end
diff --git a/spec/finders/group_members_finder_spec.rb b/spec/finders/group_members_finder_spec.rb
new file mode 100644
index 00000000000..b762756f9ce
--- /dev/null
+++ b/spec/finders/group_members_finder_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe GroupMembersFinder, '#execute' do
+ let(:group) { create(:group) }
+ let(:nested_group) { create(:group, :access_requestable, parent: group) }
+ let(:user1) { create(:user) }
+ let(:user2) { create(:user) }
+ let(:user3) { create(:user) }
+ let(:user4) { create(:user) }
+
+ it 'returns members for top-level group' do
+ member1 = group.add_master(user1)
+ member2 = group.add_master(user2)
+ member3 = group.add_master(user3)
+
+ result = described_class.new(group).execute
+
+ expect(result.to_a).to eq([member3, member2, member1])
+ end
+
+ it 'returns members for nested group' do
+ group.add_master(user2)
+ nested_group.request_access(user4)
+ member1 = group.add_master(user1)
+ member3 = nested_group.add_master(user2)
+ member4 = nested_group.add_master(user3)
+
+ result = described_class.new(nested_group).execute
+
+ expect(result.to_a).to eq([member4, member3, member1])
+ end
+end
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index 12ab1d6dde8..2a008427478 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -136,10 +136,10 @@ describe IssuesFinder do
end
end
- context 'filtering by issue iid' do
- let(:params) { { search: issue3.to_reference } }
+ context 'filtering by issues iids' do
+ let(:params) { { iids: issue3.iid } }
- it 'returns issue with iid match' do
+ it 'returns issues with iids match' do
expect(issues).to contain_exactly(issue3)
end
end
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index 3dcd7781e5b..21ef94ac5d1 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -38,5 +38,13 @@ describe MergeRequestsFinder do
merge_requests = MergeRequestsFinder.new(user, params).execute
expect(merge_requests.size).to eq(3)
end
+
+ it 'filters by iid' do
+ params = { project_id: project1.id, iids: merge_request1.iid }
+
+ merge_requests = MergeRequestsFinder.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(merge_request1)
+ end
end
end
diff --git a/spec/finders/notes_finder_spec.rb b/spec/finders/notes_finder_spec.rb
index f8b05d4e9bc..77a04507be1 100644
--- a/spec/finders/notes_finder_spec.rb
+++ b/spec/finders/notes_finder_spec.rb
@@ -111,7 +111,7 @@ describe NotesFinder do
end
it 'raises an exception for an invalid target_type' do
- params.merge!(target_type: 'invalid')
+ params[:target_type] = 'invalid'
expect { described_class.new(project, user, params).execute }.to raise_error('invalid target_type')
end
diff --git a/spec/finders/pipelines_finder_spec.rb b/spec/finders/pipelines_finder_spec.rb
index fdc8215aa47..6bada7b3eb9 100644
--- a/spec/finders/pipelines_finder_spec.rb
+++ b/spec/finders/pipelines_finder_spec.rb
@@ -39,8 +39,8 @@ describe PipelinesFinder do
end
end
- # Scoping to running will speed up the test as it doesn't hit the FS
- let(:params) { { scope: 'running' } }
+ # Scoping to pending will speed up the test as it doesn't hit the FS
+ let(:params) { { scope: 'pending' } }
it 'orders in descending order on ID' do
feature_pipeline = create(:ci_pipeline, project: project, ref: 'feature')
diff --git a/spec/fixtures/api/schemas/user/login.json b/spec/fixtures/api/schemas/user/login.json
index e6c1d9c9d84..6181b3ccc86 100644
--- a/spec/fixtures/api/schemas/user/login.json
+++ b/spec/fixtures/api/schemas/user/login.json
@@ -19,7 +19,6 @@
"organization",
"last_sign_in_at",
"confirmed_at",
- "theme_id",
"color_scheme_id",
"projects_limit",
"current_sign_in_at",
diff --git a/spec/fixtures/api/schemas/user/public.json b/spec/fixtures/api/schemas/user/public.json
index dbd5d32e89c..5587cfec61a 100644
--- a/spec/fixtures/api/schemas/user/public.json
+++ b/spec/fixtures/api/schemas/user/public.json
@@ -19,7 +19,6 @@
"organization",
"last_sign_in_at",
"confirmed_at",
- "theme_id",
"color_scheme_id",
"projects_limit",
"current_sign_in_at",
@@ -32,14 +31,14 @@
"properties": {
"id": { "type": "integer" },
"username": { "type": "string" },
- "email": {
+ "email": {
"type": "string",
"pattern": "^[^@]+@[^@]+$"
},
"name": { "type": "string" },
- "state": {
+ "state": {
"type": "string",
- "enum": ["active", "blocked"]
+ "enum": ["active", "blocked"]
},
"avatar_url": { "type": "string" },
"web_url": { "type": "string" },
@@ -54,18 +53,17 @@
"organization": { "type": ["string", "null"] },
"last_sign_in_at": { "type": "date" },
"confirmed_at": { "type": ["date", "null"] },
- "theme_id": { "type": "integer" },
"color_scheme_id": { "type": "integer" },
"projects_limit": { "type": "integer" },
"current_sign_in_at": { "type": "date" },
- "identities": {
+ "identities": {
"type": "array",
"items": {
"type": "object",
"properties": {
- "provider": {
+ "provider": {
"type": "string",
- "enum": ["github", "bitbucket", "google_oauth2"]
+ "enum": ["github", "bitbucket", "google_oauth2"]
},
"extern_uid": { "type": ["number", "string"] }
}
@@ -74,6 +72,6 @@
"can_create_group": { "type": "boolean" },
"can_create_project": { "type": "boolean" },
"two_factor_enabled": { "type": "boolean" },
- "external": { "type": "boolean" }
+ "external": { "type": "boolean" }
}
}
diff --git a/spec/fixtures/mail_room_disabled.yml b/spec/fixtures/config/mail_room_disabled.yml
index 97f8cff051f..97f8cff051f 100644
--- a/spec/fixtures/mail_room_disabled.yml
+++ b/spec/fixtures/config/mail_room_disabled.yml
diff --git a/spec/fixtures/mail_room_enabled.yml b/spec/fixtures/config/mail_room_enabled.yml
index 9c94649244d..9c94649244d 100644
--- a/spec/fixtures/mail_room_enabled.yml
+++ b/spec/fixtures/config/mail_room_enabled.yml
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 8b201f348f1..4ffdd530171 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -58,7 +58,7 @@ describe ApplicationHelper do
project = create(:empty_project, avatar: File.open(uploaded_image_temp_path))
avatar_url = "http://#{Gitlab.config.gitlab.host}/uploads/project/avatar/#{project.id}/banana_sample.gif"
- expect(helper.project_icon("#{project.namespace.to_param}/#{project.to_param}").to_s).
+ expect(helper.project_icon(project.full_path).to_s).
to eq "<img src=\"#{avatar_url}\" alt=\"Banana sample\" />"
end
@@ -68,7 +68,7 @@ describe ApplicationHelper do
allow_any_instance_of(Project).to receive(:avatar_in_git).and_return(true)
avatar_url = "http://#{Gitlab.config.gitlab.host}#{namespace_project_avatar_path(project.namespace, project)}"
- expect(helper.project_icon("#{project.namespace.to_param}/#{project.to_param}").to_s).to match(
+ expect(helper.project_icon(project.full_path).to_s).to match(
image_tag(avatar_url))
end
end
@@ -265,4 +265,9 @@ describe ApplicationHelper do
expect(helper.render_markup('foo.adoc', content)).to eq('NOEL')
end
end
+
+ describe '#active_when' do
+ it { expect(helper.active_when(true)).to eq('active') }
+ it { expect(helper.active_when(false)).to eq(nil) }
+ end
end
diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb
index 49ea4fa6d3e..cd3281d6f51 100644
--- a/spec/helpers/auth_helper_spec.rb
+++ b/spec/helpers/auth_helper_spec.rb
@@ -55,7 +55,7 @@ describe AuthHelper do
context 'all the button based providers are disabled via application_setting' do
it 'returns false' do
stub_application_setting(
- disabled_oauth_sign_in_sources: ['github', 'twitter']
+ disabled_oauth_sign_in_sources: %w(github twitter)
)
expect(helper.button_based_providers_enabled?).to be false
diff --git a/spec/helpers/emails_helper_spec.rb b/spec/helpers/emails_helper_spec.rb
index 3223556e1d3..cd112dbb2fb 100644
--- a/spec/helpers/emails_helper_spec.rb
+++ b/spec/helpers/emails_helper_spec.rb
@@ -43,4 +43,36 @@ describe EmailsHelper do
end
end
end
+
+ describe '#header_logo' do
+ context 'there is a brand item with a logo' do
+ it 'returns the brand header logo' do
+ appearance = create :appearance, header_logo: fixture_file_upload(
+ Rails.root.join('spec/fixtures/dk.png')
+ )
+
+ expect(header_logo).to eq(
+ %{<img style="height: 50px" src="/uploads/appearance/header_logo/#{appearance.id}/dk.png" alt="Dk" />}
+ )
+ end
+ end
+
+ context 'there is a brand item without a logo' do
+ it 'returns the default header logo' do
+ create :appearance, header_logo: nil
+
+ expect(header_logo).to eq(
+ %{<img alt="GitLab" src="/images/mailers/gitlab_header_logo.gif" width="55" height="50" />}
+ )
+ end
+ end
+
+ context 'there is no brand item' do
+ it 'returns the default header logo' do
+ expect(header_logo).to eq(
+ %{<img alt="GitLab" src="/images/mailers/gitlab_header_logo.gif" width="55" height="50" />}
+ )
+ end
+ end
+ end
end
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index df71680e44c..93bb711f29a 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -51,7 +51,7 @@ describe IssuablesHelper do
utf8: '✓',
author_id: '11',
assignee_id: '18',
- label_name: ['bug', 'discussion', 'documentation'],
+ label_name: %w(bug discussion documentation),
milestone_title: 'v4.0',
sort: 'due_date_asc',
namespace_id: 'gitlab-org',
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb
index 13fb9c1f1a7..88d853935c7 100644
--- a/spec/helpers/issues_helper_spec.rb
+++ b/spec/helpers/issues_helper_spec.rb
@@ -55,8 +55,8 @@ describe IssuesHelper do
describe "merge_requests_sentence" do
subject { merge_requests_sentence(merge_requests)}
let(:merge_requests) do
- [ build(:merge_request, iid: 1), build(:merge_request, iid: 2),
- build(:merge_request, iid: 3)]
+ [build(:merge_request, iid: 1), build(:merge_request, iid: 2),
+ build(:merge_request, iid: 3)]
end
it { is_expected.to eq("!1, !2, or !3") }
@@ -113,7 +113,7 @@ describe IssuesHelper do
describe "awards_sort" do
it "sorts a hash so thumbsup and thumbsdown are always on top" do
data = { "thumbsdown" => "some value", "lifter" => "some value", "thumbsup" => "some value" }
- expect(awards_sort(data).keys).to eq(["thumbsup", "thumbsdown", "lifter"])
+ expect(awards_sort(data).keys).to eq(%w(thumbsup thumbsdown lifter))
end
end
diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb
index 8d268dbcf36..25f23826648 100644
--- a/spec/helpers/merge_requests_helper_spec.rb
+++ b/spec/helpers/merge_requests_helper_spec.rb
@@ -79,4 +79,86 @@ describe MergeRequestsHelper do
expect(mr_widget_refresh_url(nil)).to eq('')
end
end
+
+ describe '#mr_closes_issues' do
+ let(:user_1) { create(:user) }
+ let(:user_2) { create(:user) }
+
+ let(:project_1) { create(:project, :private, creator: user_1, namespace: user_1.namespace) }
+ let(:project_2) { create(:project, :private, creator: user_2, namespace: user_2.namespace) }
+
+ let(:issue_1) { create(:issue, project: project_1) }
+ let(:issue_2) { create(:issue, project: project_2) }
+
+ let(:merge_request) { create(:merge_request, source_project: project_1, target_project: project_1,) }
+
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: project_1, target_project: project_1,
+ description: "Fixes #{issue_1.to_reference} Fixes #{issue_2.to_reference(project_1)}")
+ end
+
+ before do
+ project_1.team << [user_2, :developer]
+ project_2.team << [user_2, :developer]
+ allow(merge_request.project).to receive(:default_branch).and_return(merge_request.target_branch)
+ @merge_request = merge_request
+ end
+
+ context 'user without access to another private project' do
+ let(:current_user) { user_1 }
+
+ it 'cannot see that project\'s issue that will be closed on acceptance' do
+ expect(mr_closes_issues).to contain_exactly(issue_1)
+ end
+ end
+
+ context 'user with access to another private project' do
+ let(:current_user) { user_2 }
+
+ it 'can see that project\'s issue that will be closed on acceptance' do
+ expect(mr_closes_issues).to contain_exactly(issue_1, issue_2)
+ end
+ end
+ end
+
+ describe '#mr_issues_mentioned_but_not_closing' do
+ let(:user_1) { create(:user) }
+ let(:user_2) { create(:user) }
+
+ let(:project_1) { create(:project, :private, creator: user_1, namespace: user_1.namespace) }
+ let(:project_2) { create(:project, :private, creator: user_2, namespace: user_2.namespace) }
+
+ let(:issue_1) { create(:issue, project: project_1) }
+ let(:issue_2) { create(:issue, project: project_2) }
+
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: project_1, target_project: project_1,
+ description: "#{issue_1.to_reference} #{issue_2.to_reference(project_1)}")
+ end
+
+ before do
+ project_1.team << [user_2, :developer]
+ project_2.team << [user_2, :developer]
+ allow(merge_request.project).to receive(:default_branch).and_return(merge_request.target_branch)
+ @merge_request = merge_request
+ end
+
+ context 'user without access to another private project' do
+ let(:current_user) { user_1 }
+
+ it 'cannot see that project\'s issue that will be closed on acceptance' do
+ expect(mr_issues_mentioned_but_not_closing).to contain_exactly(issue_1)
+ end
+ end
+
+ context 'user with access to another private project' do
+ let(:current_user) { user_2 }
+
+ it 'can see that project\'s issue that will be closed on acceptance' do
+ expect(mr_issues_mentioned_but_not_closing).to contain_exactly(issue_1, issue_2)
+ end
+ end
+ end
end
diff --git a/spec/helpers/page_layout_helper_spec.rb b/spec/helpers/page_layout_helper_spec.rb
index dc07657e101..2cc0b40b2d0 100644
--- a/spec/helpers/page_layout_helper_spec.rb
+++ b/spec/helpers/page_layout_helper_spec.rb
@@ -40,6 +40,18 @@ describe PageLayoutHelper do
end
end
+ describe 'favicon' do
+ it 'defaults to favicon.ico' do
+ allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('production'))
+ expect(helper.favicon).to eq 'favicon.ico'
+ end
+
+ it 'has blue favicon for development' do
+ allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('development'))
+ expect(helper.favicon).to eq 'favicon-blue.ico'
+ end
+ end
+
describe 'page_image' do
it 'defaults to the GitLab logo' do
expect(helper.page_image).to end_with 'assets/gitlab_logo.png'
diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb
index 1f02e06e312..f3e79cc7290 100644
--- a/spec/helpers/preferences_helper_spec.rb
+++ b/spec/helpers/preferences_helper_spec.rb
@@ -26,32 +26,6 @@ describe PreferencesHelper do
end
end
- describe 'user_application_theme' do
- context 'with a user' do
- it "returns user's theme's css_class" do
- stub_user(theme_id: 3)
-
- expect(helper.user_application_theme).to eq 'ui_green'
- end
-
- it 'returns the default when id is invalid' do
- stub_user(theme_id: Gitlab::Themes.count + 5)
-
- allow(Gitlab.config.gitlab).to receive(:default_theme).and_return(2)
-
- expect(helper.user_application_theme).to eq 'ui_charcoal'
- end
- end
-
- context 'without a user' do
- it 'returns the default theme' do
- stub_user
-
- expect(helper.user_application_theme).to eq Gitlab::Themes.default.css_class
- end
- end
- end
-
describe 'user_color_scheme' do
context 'with a user' do
it "returns user's scheme's css_class" do
diff --git a/spec/helpers/submodule_helper_spec.rb b/spec/helpers/submodule_helper_spec.rb
index 4da1569e59f..28b8def331d 100644
--- a/spec/helpers/submodule_helper_spec.rb
+++ b/spec/helpers/submodule_helper_spec.rb
@@ -20,97 +20,97 @@ describe SubmoduleHelper do
it 'detects ssh on standard port' do
allow(Gitlab.config.gitlab_shell).to receive(:ssh_port).and_return(22) # set this just to be sure
allow(Gitlab.config.gitlab_shell).to receive(:ssh_path_prefix).and_return(Settings.send(:build_gitlab_shell_ssh_path_prefix))
- stub_url([ config.user, '@', config.host, ':gitlab-org/gitlab-ce.git' ].join(''))
- expect(submodule_links(submodule_item)).to eq([ namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash') ])
+ stub_url([config.user, '@', config.host, ':gitlab-org/gitlab-ce.git'].join(''))
+ expect(submodule_links(submodule_item)).to eq([namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash')])
end
it 'detects ssh on non-standard port' do
allow(Gitlab.config.gitlab_shell).to receive(:ssh_port).and_return(2222)
allow(Gitlab.config.gitlab_shell).to receive(:ssh_path_prefix).and_return(Settings.send(:build_gitlab_shell_ssh_path_prefix))
- stub_url([ 'ssh://', config.user, '@', config.host, ':2222/gitlab-org/gitlab-ce.git' ].join(''))
- expect(submodule_links(submodule_item)).to eq([ namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash') ])
+ stub_url(['ssh://', config.user, '@', config.host, ':2222/gitlab-org/gitlab-ce.git'].join(''))
+ expect(submodule_links(submodule_item)).to eq([namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash')])
end
it 'detects http on standard port' do
allow(Gitlab.config.gitlab).to receive(:port).and_return(80)
allow(Gitlab.config.gitlab).to receive(:url).and_return(Settings.send(:build_gitlab_url))
- stub_url([ 'http://', config.host, '/gitlab-org/gitlab-ce.git' ].join(''))
- expect(submodule_links(submodule_item)).to eq([ namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash') ])
+ stub_url(['http://', config.host, '/gitlab-org/gitlab-ce.git'].join(''))
+ expect(submodule_links(submodule_item)).to eq([namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash')])
end
it 'detects http on non-standard port' do
allow(Gitlab.config.gitlab).to receive(:port).and_return(3000)
allow(Gitlab.config.gitlab).to receive(:url).and_return(Settings.send(:build_gitlab_url))
- stub_url([ 'http://', config.host, ':3000/gitlab-org/gitlab-ce.git' ].join(''))
- expect(submodule_links(submodule_item)).to eq([ namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash') ])
+ stub_url(['http://', config.host, ':3000/gitlab-org/gitlab-ce.git'].join(''))
+ expect(submodule_links(submodule_item)).to eq([namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash')])
end
it 'works with relative_url_root' do
allow(Gitlab.config.gitlab).to receive(:port).and_return(80) # set this just to be sure
allow(Gitlab.config.gitlab).to receive(:relative_url_root).and_return('/gitlab/root')
allow(Gitlab.config.gitlab).to receive(:url).and_return(Settings.send(:build_gitlab_url))
- stub_url([ 'http://', config.host, '/gitlab/root/gitlab-org/gitlab-ce.git' ].join(''))
- expect(submodule_links(submodule_item)).to eq([ namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash') ])
+ stub_url(['http://', config.host, '/gitlab/root/gitlab-org/gitlab-ce.git'].join(''))
+ expect(submodule_links(submodule_item)).to eq([namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash')])
end
end
context 'submodule on github.com' do
it 'detects ssh' do
stub_url('git@github.com:gitlab-org/gitlab-ce.git')
- expect(submodule_links(submodule_item)).to eq([ 'https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash' ])
+ expect(submodule_links(submodule_item)).to eq(['https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash'])
end
it 'detects http' do
stub_url('http://github.com/gitlab-org/gitlab-ce.git')
- expect(submodule_links(submodule_item)).to eq([ 'https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash' ])
+ expect(submodule_links(submodule_item)).to eq(['https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash'])
end
it 'detects https' do
stub_url('https://github.com/gitlab-org/gitlab-ce.git')
- expect(submodule_links(submodule_item)).to eq([ 'https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash' ])
+ expect(submodule_links(submodule_item)).to eq(['https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash'])
end
it 'returns original with non-standard url' do
stub_url('http://github.com/gitlab-org/gitlab-ce')
- expect(submodule_links(submodule_item)).to eq([ repo.submodule_url_for, nil ])
+ expect(submodule_links(submodule_item)).to eq([repo.submodule_url_for, nil])
stub_url('http://github.com/another/gitlab-org/gitlab-ce.git')
- expect(submodule_links(submodule_item)).to eq([ repo.submodule_url_for, nil ])
+ expect(submodule_links(submodule_item)).to eq([repo.submodule_url_for, nil])
end
end
context 'submodule on gitlab.com' do
it 'detects ssh' do
stub_url('git@gitlab.com:gitlab-org/gitlab-ce.git')
- expect(submodule_links(submodule_item)).to eq([ 'https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash' ])
+ expect(submodule_links(submodule_item)).to eq(['https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash'])
end
it 'detects http' do
stub_url('http://gitlab.com/gitlab-org/gitlab-ce.git')
- expect(submodule_links(submodule_item)).to eq([ 'https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash' ])
+ expect(submodule_links(submodule_item)).to eq(['https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash'])
end
it 'detects https' do
stub_url('https://gitlab.com/gitlab-org/gitlab-ce.git')
- expect(submodule_links(submodule_item)).to eq([ 'https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash' ])
+ expect(submodule_links(submodule_item)).to eq(['https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash'])
end
it 'returns original with non-standard url' do
stub_url('http://gitlab.com/gitlab-org/gitlab-ce')
- expect(submodule_links(submodule_item)).to eq([ repo.submodule_url_for, nil ])
+ expect(submodule_links(submodule_item)).to eq([repo.submodule_url_for, nil])
stub_url('http://gitlab.com/another/gitlab-org/gitlab-ce.git')
- expect(submodule_links(submodule_item)).to eq([ repo.submodule_url_for, nil ])
+ expect(submodule_links(submodule_item)).to eq([repo.submodule_url_for, nil])
end
end
context 'submodule on unsupported' do
it 'returns original' do
stub_url('http://mygitserver.com/gitlab-org/gitlab-ce')
- expect(submodule_links(submodule_item)).to eq([ repo.submodule_url_for, nil ])
+ expect(submodule_links(submodule_item)).to eq([repo.submodule_url_for, nil])
stub_url('http://mygitserver.com/gitlab-org/gitlab-ce.git')
- expect(submodule_links(submodule_item)).to eq([ repo.submodule_url_for, nil ])
+ expect(submodule_links(submodule_item)).to eq([repo.submodule_url_for, nil])
end
end
diff --git a/spec/helpers/version_check_helper_spec.rb b/spec/helpers/version_check_helper_spec.rb
new file mode 100644
index 00000000000..889fe441171
--- /dev/null
+++ b/spec/helpers/version_check_helper_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe VersionCheckHelper do
+ describe '#version_status_badge' do
+ it 'should return nil if not dev environment and not enabled' do
+ allow(Rails.env).to receive(:production?) { false }
+ allow(current_application_settings).to receive(:version_check_enabled) { false }
+
+ expect(helper.version_status_badge).to be(nil)
+ end
+
+ context 'when production and enabled' do
+ before do
+ allow(Rails.env).to receive(:production?) { true }
+ allow(current_application_settings).to receive(:version_check_enabled) { true }
+ allow_any_instance_of(VersionCheck).to receive(:url) { 'https://version.host.com/check.svg?gitlab_info=xxx' }
+
+ @image_tag = helper.version_status_badge
+ end
+
+ it 'should return an image tag' do
+ expect(@image_tag).to match(/^<img/)
+ end
+
+ it 'should have a js prefixed css class' do
+ expect(@image_tag).to match(/class="js-version-status-badge"/)
+ end
+
+ it 'should have a VersionCheck url as the src' do
+ expect(@image_tag).to match(/src="https:\/\/version\.host\.com\/check\.svg\?gitlab_info=xxx"/)
+ end
+ end
+ end
+end
diff --git a/spec/helpers/wiki_helper_spec.rb b/spec/helpers/wiki_helper_spec.rb
new file mode 100644
index 00000000000..92c6f27a867
--- /dev/null
+++ b/spec/helpers/wiki_helper_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe WikiHelper do
+ describe '#breadcrumb' do
+ context 'when the page is at the root level' do
+ it 'returns the capitalized page name' do
+ slug = 'page-name'
+
+ expect(helper.breadcrumb(slug)).to eq('Page name')
+ end
+ end
+
+ context 'when the page is inside a directory' do
+ it 'returns the capitalized name of each directory and of the page itself' do
+ slug = 'dir_1/page-name'
+
+ expect(helper.breadcrumb(slug)).to eq('Dir_1 / Page name')
+ end
+ end
+ end
+end
diff --git a/spec/initializers/trusted_proxies_spec.rb b/spec/initializers/trusted_proxies_spec.rb
index 290e47763eb..ff8b8daa347 100644
--- a/spec/initializers/trusted_proxies_spec.rb
+++ b/spec/initializers/trusted_proxies_spec.rb
@@ -27,7 +27,7 @@ describe 'trusted_proxies', lib: true do
context 'with private IP ranges added' do
before do
- set_trusted_proxies([ "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16" ])
+ set_trusted_proxies(["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"])
end
it 'filters out private and local IPs' do
@@ -39,7 +39,7 @@ describe 'trusted_proxies', lib: true do
context 'with proxy IP added' do
before do
- set_trusted_proxies([ "60.98.25.47" ])
+ set_trusted_proxies(["60.98.25.47"])
end
it 'filters out proxy IP' do
diff --git a/spec/javascripts/.eslintrc b/spec/javascripts/.eslintrc
index fbd9bb9f0ff..3d922021978 100644
--- a/spec/javascripts/.eslintrc
+++ b/spec/javascripts/.eslintrc
@@ -18,7 +18,8 @@
"sandbox": false,
"setFixtures": false,
"setStyleFixtures": false,
- "spyOnEvent": false
+ "spyOnEvent": false,
+ "ClassSpecHelper": false
},
"plugins": ["jasmine"],
"rules": {
diff --git a/spec/javascripts/ajax_loading_spinner_spec.js b/spec/javascripts/ajax_loading_spinner_spec.js
new file mode 100644
index 00000000000..a68bccb16f4
--- /dev/null
+++ b/spec/javascripts/ajax_loading_spinner_spec.js
@@ -0,0 +1,58 @@
+require('~/extensions/array');
+require('jquery');
+require('jquery-ujs');
+require('~/ajax_loading_spinner');
+
+describe('Ajax Loading Spinner', () => {
+ const fixtureTemplate = 'static/ajax_loading_spinner.html.raw';
+ preloadFixtures(fixtureTemplate);
+
+ beforeEach(() => {
+ loadFixtures(fixtureTemplate);
+ gl.AjaxLoadingSpinner.init();
+ });
+
+ it('change current icon with spinner icon and disable link while waiting ajax response', (done) => {
+ spyOn(jQuery, 'ajax').and.callFake((req) => {
+ const xhr = new XMLHttpRequest();
+ const ajaxLoadingSpinner = document.querySelector('.js-ajax-loading-spinner');
+ const icon = ajaxLoadingSpinner.querySelector('i');
+
+ req.beforeSend(xhr, { dataType: 'text/html' });
+
+ expect(icon).not.toHaveClass('fa-trash-o');
+ expect(icon).toHaveClass('fa-spinner');
+ expect(icon).toHaveClass('fa-spin');
+ expect(icon.dataset.icon).toEqual('fa-trash-o');
+ expect(ajaxLoadingSpinner.getAttribute('disabled')).toEqual('');
+
+ req.complete({});
+
+ done();
+ const deferred = $.Deferred();
+ return deferred.promise();
+ });
+ document.querySelector('.js-ajax-loading-spinner').click();
+ });
+
+ it('use original icon again and enabled the link after complete the ajax request', (done) => {
+ spyOn(jQuery, 'ajax').and.callFake((req) => {
+ const xhr = new XMLHttpRequest();
+ const ajaxLoadingSpinner = document.querySelector('.js-ajax-loading-spinner');
+
+ req.beforeSend(xhr, { dataType: 'text/html' });
+ req.complete({});
+
+ const icon = ajaxLoadingSpinner.querySelector('i');
+ expect(icon).toHaveClass('fa-trash-o');
+ expect(icon).not.toHaveClass('fa-spinner');
+ expect(icon).not.toHaveClass('fa-spin');
+ expect(ajaxLoadingSpinner.getAttribute('disabled')).toEqual(null);
+
+ done();
+ const deferred = $.Deferred();
+ return deferred.promise();
+ });
+ document.querySelector('.js-ajax-loading-spinner').click();
+ });
+});
diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js
index 001cd8d6325..e5826f9c29f 100644
--- a/spec/javascripts/awards_handler_spec.js
+++ b/spec/javascripts/awards_handler_spec.js
@@ -229,4 +229,4 @@ require('./fixtures/emoji_menu');
});
});
});
-}).call(this);
+}).call(window);
diff --git a/spec/javascripts/behaviors/autosize_spec.js b/spec/javascripts/behaviors/autosize_spec.js
index 4a3da9e318b..3deaf258cae 100644
--- a/spec/javascripts/behaviors/autosize_spec.js
+++ b/spec/javascripts/behaviors/autosize_spec.js
@@ -18,4 +18,4 @@ require('~/behaviors/autosize');
return $(document).trigger('load');
};
});
-}).call(this);
+}).call(window);
diff --git a/spec/javascripts/behaviors/quick_submit_spec.js b/spec/javascripts/behaviors/quick_submit_spec.js
index b84126c0e3d..4820ce41ade 100644
--- a/spec/javascripts/behaviors/quick_submit_spec.js
+++ b/spec/javascripts/behaviors/quick_submit_spec.js
@@ -5,73 +5,83 @@ require('~/behaviors/quick_submit');
(function() {
describe('Quick Submit behavior', function() {
var keydownEvent;
- preloadFixtures('static/behaviors/quick_submit.html.raw');
+ preloadFixtures('issues/open-issue.html.raw');
beforeEach(function() {
- loadFixtures('static/behaviors/quick_submit.html.raw');
+ loadFixtures('issues/open-issue.html.raw');
$('form').submit(function(e) {
// Prevent a form submit from moving us off the testing page
return e.preventDefault();
});
- return this.spies = {
+ this.spies = {
submit: spyOnEvent('form', 'submit')
};
+
+ this.textarea = $('.js-quick-submit textarea').first();
});
it('does not respond to other keyCodes', function() {
- $('input.quick-submit-input').trigger(keydownEvent({
+ this.textarea.trigger(keydownEvent({
keyCode: 32
}));
return expect(this.spies.submit).not.toHaveBeenTriggered();
});
it('does not respond to Enter alone', function() {
- $('input.quick-submit-input').trigger(keydownEvent({
+ this.textarea.trigger(keydownEvent({
ctrlKey: false,
metaKey: false
}));
return expect(this.spies.submit).not.toHaveBeenTriggered();
});
it('does not respond to repeated events', function() {
- $('input.quick-submit-input').trigger(keydownEvent({
+ this.textarea.trigger(keydownEvent({
repeat: true
}));
return expect(this.spies.submit).not.toHaveBeenTriggered();
});
- it('disables submit buttons', function() {
- $('textarea').trigger(keydownEvent());
- expect($('input[type=submit]')).toBeDisabled();
- return expect($('button[type=submit]')).toBeDisabled();
+ it('disables input of type submit', function() {
+ const submitButton = $('.js-quick-submit input[type=submit]');
+ this.textarea.trigger(keydownEvent());
+ expect(submitButton).toBeDisabled();
+ });
+ it('disables button of type submit', function() {
+ // button doesn't exist in fixture, add it manually
+ const submitButton = $('<button type="submit">Submit it</button>');
+ submitButton.insertAfter(this.textarea);
+
+ this.textarea.trigger(keydownEvent());
+ expect(submitButton).toBeDisabled();
});
- // We cannot stub `navigator.userAgent` for CI's `rake teaspoon` task, so we'll
+ // We cannot stub `navigator.userAgent` for CI's `rake karma` task, so we'll
// only run the tests that apply to the current platform
if (navigator.userAgent.match(/Macintosh/)) {
it('responds to Meta+Enter', function() {
- $('input.quick-submit-input').trigger(keydownEvent());
+ this.textarea.trigger(keydownEvent());
return expect(this.spies.submit).toHaveBeenTriggered();
});
it('excludes other modifier keys', function() {
- $('input.quick-submit-input').trigger(keydownEvent({
+ this.textarea.trigger(keydownEvent({
altKey: true
}));
- $('input.quick-submit-input').trigger(keydownEvent({
+ this.textarea.trigger(keydownEvent({
ctrlKey: true
}));
- $('input.quick-submit-input').trigger(keydownEvent({
+ this.textarea.trigger(keydownEvent({
shiftKey: true
}));
return expect(this.spies.submit).not.toHaveBeenTriggered();
});
} else {
it('responds to Ctrl+Enter', function() {
- $('input.quick-submit-input').trigger(keydownEvent());
+ this.textarea.trigger(keydownEvent());
return expect(this.spies.submit).toHaveBeenTriggered();
});
it('excludes other modifier keys', function() {
- $('input.quick-submit-input').trigger(keydownEvent({
+ this.textarea.trigger(keydownEvent({
altKey: true
}));
- $('input.quick-submit-input').trigger(keydownEvent({
+ this.textarea.trigger(keydownEvent({
metaKey: true
}));
- $('input.quick-submit-input').trigger(keydownEvent({
+ this.textarea.trigger(keydownEvent({
shiftKey: true
}));
return expect(this.spies.submit).not.toHaveBeenTriggered();
@@ -93,4 +103,4 @@ require('~/behaviors/quick_submit');
return $.Event('keydown', $.extend({}, defaults, options));
};
});
-}).call(this);
+}).call(window);
diff --git a/spec/javascripts/behaviors/requires_input_spec.js b/spec/javascripts/behaviors/requires_input_spec.js
index a958ac76e66..3a84013a2ed 100644
--- a/spec/javascripts/behaviors/requires_input_spec.js
+++ b/spec/javascripts/behaviors/requires_input_spec.js
@@ -4,18 +4,19 @@ require('~/behaviors/requires_input');
(function() {
describe('requiresInput', function() {
- preloadFixtures('static/behaviors/requires_input.html.raw');
+ preloadFixtures('branches/new_branch.html.raw');
beforeEach(function() {
- return loadFixtures('static/behaviors/requires_input.html.raw');
+ loadFixtures('branches/new_branch.html.raw');
+ this.submitButton = $('button[type="submit"]');
});
it('disables submit when any field is required', function() {
$('.js-requires-input').requiresInput();
- return expect($('.submit')).toBeDisabled();
+ return expect(this.submitButton).toBeDisabled();
});
it('enables submit when no field is required', function() {
$('*[required=required]').removeAttr('required');
$('.js-requires-input').requiresInput();
- return expect($('.submit')).not.toBeDisabled();
+ return expect(this.submitButton).not.toBeDisabled();
});
it('enables submit when all required fields are pre-filled', function() {
$('*[required=required]').remove();
@@ -25,9 +26,9 @@ require('~/behaviors/requires_input');
it('enables submit when all required fields receive input', function() {
$('.js-requires-input').requiresInput();
$('#required1').val('input1').change();
- expect($('.submit')).toBeDisabled();
+ expect(this.submitButton).toBeDisabled();
$('#optional1').val('input1').change();
- expect($('.submit')).toBeDisabled();
+ expect(this.submitButton).toBeDisabled();
$('#required2').val('input2').change();
$('#required3').val('input3').change();
$('#required4').val('input4').change();
@@ -35,4 +36,4 @@ require('~/behaviors/requires_input');
return expect($('.submit')).not.toBeDisabled();
});
});
-}).call(this);
+}).call(window);
diff --git a/spec/javascripts/commit/pipelines/pipelines_store_spec.js.es6 b/spec/javascripts/commit/pipelines/pipelines_store_spec.js.es6
index 559723bafcc..94973419979 100644
--- a/spec/javascripts/commit/pipelines/pipelines_store_spec.js.es6
+++ b/spec/javascripts/commit/pipelines/pipelines_store_spec.js.es6
@@ -1,12 +1,15 @@
-require('~/commit/pipelines/pipelines_store');
+const PipelinesStore = require('~/commit/pipelines/pipelines_store');
describe('Store', () => {
let store;
beforeEach(() => {
- store = new gl.commits.pipelines.PipelinesStore();
+ store = new PipelinesStore();
});
+ // unregister intervals and event handlers
+ afterEach(() => gl.VueRealtimeListener.reset());
+
it('should start with a blank state', () => {
expect(store.state.pipelines.length).toBe(0);
});
diff --git a/spec/javascripts/dashboard_spec.js.es6 b/spec/javascripts/dashboard_spec.js.es6
deleted file mode 100644
index c0bdb89ed63..00000000000
--- a/spec/javascripts/dashboard_spec.js.es6
+++ /dev/null
@@ -1,37 +0,0 @@
-/* eslint-disable no-new */
-
-require('~/sidebar');
-require('~/lib/utils/text_utility');
-
-((global) => {
- describe('Dashboard', () => {
- const fixtureTemplate = 'static/dashboard.html.raw';
-
- function todosCountText() {
- return $('.js-todos-count').text();
- }
-
- function triggerToggle(newCount) {
- $(document).trigger('todo:toggle', newCount);
- }
-
- preloadFixtures(fixtureTemplate);
- beforeEach(() => {
- loadFixtures(fixtureTemplate);
- new global.Sidebar();
- });
-
- it('should update todos-count after receiving the todo:toggle event', () => {
- triggerToggle(5);
- expect(todosCountText()).toEqual('5');
- });
-
- it('should display todos-count with delimiter', () => {
- triggerToggle(1000);
- expect(todosCountText()).toEqual('1,000');
-
- triggerToggle(1000000);
- expect(todosCountText()).toEqual('1,000,000');
- });
- });
-})(window.gl);
diff --git a/spec/javascripts/environments/environment_actions_spec.js.es6 b/spec/javascripts/environments/environment_actions_spec.js.es6
index b1838045a06..850586f9f3a 100644
--- a/spec/javascripts/environments/environment_actions_spec.js.es6
+++ b/spec/javascripts/environments/environment_actions_spec.js.es6
@@ -1,4 +1,4 @@
-require('~/environments/components/environment_actions');
+const ActionsComponent = require('~/environments/components/environment_actions');
describe('Actions Component', () => {
preloadFixtures('static/environments/element.html.raw');
@@ -19,7 +19,7 @@ describe('Actions Component', () => {
},
];
- const component = new window.gl.environmentsList.ActionsComponent({
+ const component = new ActionsComponent({
el: document.querySelector('.test-dom-element'),
propsData: {
actions: actionsMock,
@@ -47,7 +47,7 @@ describe('Actions Component', () => {
},
];
- const component = new window.gl.environmentsList.ActionsComponent({
+ const component = new ActionsComponent({
el: document.querySelector('.test-dom-element'),
propsData: {
actions: actionsMock,
diff --git a/spec/javascripts/environments/environment_external_url_spec.js.es6 b/spec/javascripts/environments/environment_external_url_spec.js.es6
index a6a587e69f5..393dbb5aae0 100644
--- a/spec/javascripts/environments/environment_external_url_spec.js.es6
+++ b/spec/javascripts/environments/environment_external_url_spec.js.es6
@@ -1,4 +1,4 @@
-require('~/environments/components/environment_external_url');
+const ExternalUrlComponent = require('~/environments/components/environment_external_url');
describe('External URL Component', () => {
preloadFixtures('static/environments/element.html.raw');
@@ -8,7 +8,7 @@ describe('External URL Component', () => {
it('should link to the provided externalUrl prop', () => {
const externalURL = 'https://gitlab.com';
- const component = new window.gl.environmentsList.ExternalUrlComponent({
+ const component = new ExternalUrlComponent({
el: document.querySelector('.test-dom-element'),
propsData: {
externalUrl: externalURL,
diff --git a/spec/javascripts/environments/environment_item_spec.js.es6 b/spec/javascripts/environments/environment_item_spec.js.es6
index d87cc0996c9..7fea80ed799 100644
--- a/spec/javascripts/environments/environment_item_spec.js.es6
+++ b/spec/javascripts/environments/environment_item_spec.js.es6
@@ -1,5 +1,5 @@
-window.timeago = require('vendor/timeago');
-require('~/environments/components/environment_item');
+window.timeago = require('timeago.js');
+const EnvironmentItem = require('~/environments/components/environment_item');
describe('Environment item', () => {
preloadFixtures('static/environments/table.html.raw');
@@ -14,33 +14,16 @@ describe('Environment item', () => {
beforeEach(() => {
mockItem = {
name: 'review',
- children: [
- {
- name: 'review-app',
- id: 1,
- state: 'available',
- external_url: '',
- last_deployment: {},
- created_at: '2016-11-07T11:11:16.525Z',
- updated_at: '2016-11-10T15:55:58.778Z',
- },
- {
- name: 'production',
- id: 2,
- state: 'available',
- external_url: '',
- last_deployment: {},
- created_at: '2016-11-07T11:11:16.525Z',
- updated_at: '2016-11-10T15:55:58.778Z',
- },
- ],
+ folderName: 'review',
+ size: 3,
+ isFolder: true,
+ environment_path: 'url',
};
- component = new window.gl.environmentsList.EnvironmentItem({
+ component = new EnvironmentItem({
el: document.querySelector('tr#environment-row'),
propsData: {
model: mockItem,
- toggleRow: () => {},
canCreateDeployment: false,
canReadEnvironment: true,
},
@@ -53,7 +36,7 @@ describe('Environment item', () => {
});
it('Should render the number of children in a badge', () => {
- expect(component.$el.querySelector('.folder-name .badge').textContent).toContain(mockItem.children.length);
+ expect(component.$el.querySelector('.folder-name .badge').textContent).toContain(mockItem.size);
});
});
@@ -63,8 +46,8 @@ describe('Environment item', () => {
beforeEach(() => {
environment = {
- id: 31,
name: 'production',
+ size: 1,
state: 'stopped',
external_url: 'http://external.com',
environment_type: null,
@@ -125,11 +108,10 @@ describe('Environment item', () => {
updated_at: '2016-11-10T15:55:58.778Z',
};
- component = new window.gl.environmentsList.EnvironmentItem({
+ component = new EnvironmentItem({
el: document.querySelector('tr#environment-row'),
propsData: {
model: environment,
- toggleRow: () => {},
canCreateDeployment: true,
canReadEnvironment: true,
},
diff --git a/spec/javascripts/environments/environment_rollback_spec.js.es6 b/spec/javascripts/environments/environment_rollback_spec.js.es6
index 043b8708a6e..4a596baad09 100644
--- a/spec/javascripts/environments/environment_rollback_spec.js.es6
+++ b/spec/javascripts/environments/environment_rollback_spec.js.es6
@@ -1,4 +1,4 @@
-require('~/environments/components/environment_rollback');
+const RollbackComponent = require('~/environments/components/environment_rollback');
describe('Rollback Component', () => {
preloadFixtures('static/environments/element.html.raw');
@@ -10,7 +10,7 @@ describe('Rollback Component', () => {
});
it('Should link to the provided retryUrl', () => {
- const component = new window.gl.environmentsList.RollbackComponent({
+ const component = new RollbackComponent({
el: document.querySelector('.test-dom-element'),
propsData: {
retryUrl: retryURL,
@@ -22,7 +22,7 @@ describe('Rollback Component', () => {
});
it('Should render Re-deploy label when isLastDeployment is true', () => {
- const component = new window.gl.environmentsList.RollbackComponent({
+ const component = new RollbackComponent({
el: document.querySelector('.test-dom-element'),
propsData: {
retryUrl: retryURL,
@@ -34,7 +34,7 @@ describe('Rollback Component', () => {
});
it('Should render Rollback label when isLastDeployment is false', () => {
- const component = new window.gl.environmentsList.RollbackComponent({
+ const component = new RollbackComponent({
el: document.querySelector('.test-dom-element'),
propsData: {
retryUrl: retryURL,
diff --git a/spec/javascripts/environments/environment_spec.js.es6 b/spec/javascripts/environments/environment_spec.js.es6
index 87eda136122..edd0cad32d0 100644
--- a/spec/javascripts/environments/environment_spec.js.es6
+++ b/spec/javascripts/environments/environment_spec.js.es6
@@ -1,9 +1,7 @@
-/* global Vue, environment */
-
+const Vue = require('vue');
require('~/flash');
-require('~/environments/stores/environments_store');
-require('~/environments/components/environment');
-require('./mock_data');
+const EnvironmentsComponent = require('~/environments/components/environment');
+const { environment } = require('./mock_data');
describe('Environment', () => {
preloadFixtures('static/environments/environments.html.raw');
@@ -33,11 +31,8 @@ describe('Environment', () => {
});
it('should render the empty state', (done) => {
- component = new gl.environmentsList.EnvironmentsComponent({
+ component = new EnvironmentsComponent({
el: document.querySelector('#environments-list-view'),
- propsData: {
- store: gl.environmentsList.EnvironmentsStore.create(),
- },
});
setTimeout(() => {
@@ -54,15 +49,30 @@ describe('Environment', () => {
});
});
- describe('with environments', () => {
+ describe('with paginated environments', () => {
const environmentsResponseInterceptor = (request, next) => {
- next(request.respondWith(JSON.stringify([environment]), {
+ next(request.respondWith(JSON.stringify({
+ environments: [environment],
+ stopped_count: 1,
+ available_count: 0,
+ }), {
status: 200,
+ headers: {
+ 'X-nExt-pAge': '2',
+ 'x-page': '1',
+ 'X-Per-Page': '1',
+ 'X-Prev-Page': '',
+ 'X-TOTAL': '37',
+ 'X-Total-Pages': '2',
+ },
}));
};
beforeEach(() => {
Vue.http.interceptors.push(environmentsResponseInterceptor);
+ component = new EnvironmentsComponent({
+ el: document.querySelector('#environments-list-view'),
+ });
});
afterEach(() => {
@@ -72,13 +82,6 @@ describe('Environment', () => {
});
it('should render a table with environments', (done) => {
- component = new gl.environmentsList.EnvironmentsComponent({
- el: document.querySelector('#environments-list-view'),
- propsData: {
- store: gl.environmentsList.EnvironmentsStore.create(),
- },
- });
-
setTimeout(() => {
expect(
component.$el.querySelectorAll('table tbody tr').length,
@@ -86,6 +89,59 @@ describe('Environment', () => {
done();
}, 0);
});
+
+ describe('pagination', () => {
+ it('should render pagination', (done) => {
+ setTimeout(() => {
+ expect(
+ component.$el.querySelectorAll('.gl-pagination li').length,
+ ).toEqual(5);
+ done();
+ }, 0);
+ });
+
+ it('should update url when no search params are present', (done) => {
+ spyOn(gl.utils, 'visitUrl');
+ setTimeout(() => {
+ component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
+ expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2');
+ done();
+ }, 0);
+ });
+
+ it('should update url when page is already present', (done) => {
+ spyOn(gl.utils, 'visitUrl');
+ window.history.pushState({}, null, '?page=1');
+
+ setTimeout(() => {
+ component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
+ expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2');
+ done();
+ }, 0);
+ });
+
+ it('should update url when page and scope are already present', (done) => {
+ spyOn(gl.utils, 'visitUrl');
+ window.history.pushState({}, null, '?scope=all&page=1');
+
+ setTimeout(() => {
+ component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
+ expect(gl.utils.visitUrl).toHaveBeenCalledWith('?scope=all&page=2');
+ done();
+ }, 0);
+ });
+
+ it('should update url when page and scope are already present and page is first param', (done) => {
+ spyOn(gl.utils, 'visitUrl');
+ window.history.pushState({}, null, '?page=1&scope=all');
+
+ setTimeout(() => {
+ component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
+ expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2&scope=all');
+ done();
+ }, 0);
+ });
+ });
});
});
@@ -107,11 +163,8 @@ describe('Environment', () => {
});
it('should render empty state', (done) => {
- component = new gl.environmentsList.EnvironmentsComponent({
+ component = new EnvironmentsComponent({
el: document.querySelector('#environments-list-view'),
- propsData: {
- store: gl.environmentsList.EnvironmentsStore.create(),
- },
});
setTimeout(() => {
diff --git a/spec/javascripts/environments/environment_stop_spec.js.es6 b/spec/javascripts/environments/environment_stop_spec.js.es6
index 2dfce5ba824..5ca65b1debc 100644
--- a/spec/javascripts/environments/environment_stop_spec.js.es6
+++ b/spec/javascripts/environments/environment_stop_spec.js.es6
@@ -1,4 +1,4 @@
-require('~/environments/components/environment_stop');
+const StopComponent = require('~/environments/components/environment_stop');
describe('Stop Component', () => {
preloadFixtures('static/environments/element.html.raw');
@@ -10,7 +10,7 @@ describe('Stop Component', () => {
loadFixtures('static/environments/element.html.raw');
stopURL = '/stop';
- component = new window.gl.environmentsList.StopComponent({
+ component = new StopComponent({
el: document.querySelector('.test-dom-element'),
propsData: {
stopUrl: stopURL,
diff --git a/spec/javascripts/environments/environment_table_spec.js.es6 b/spec/javascripts/environments/environment_table_spec.js.es6
new file mode 100644
index 00000000000..be4330b5012
--- /dev/null
+++ b/spec/javascripts/environments/environment_table_spec.js.es6
@@ -0,0 +1,30 @@
+const EnvironmentTable = require('~/environments/components/environments_table');
+
+describe('Environment item', () => {
+ preloadFixtures('static/environments/element.html.raw');
+ beforeEach(() => {
+ loadFixtures('static/environments/element.html.raw');
+ });
+
+ it('Should render a table', () => {
+ const mockItem = {
+ name: 'review',
+ size: 3,
+ isFolder: true,
+ latest: {
+ environment_path: 'url',
+ },
+ };
+
+ const component = new EnvironmentTable({
+ el: document.querySelector('.test-dom-element'),
+ propsData: {
+ environments: [{ mockItem }],
+ canCreateDeployment: false,
+ canReadEnvironment: true,
+ },
+ });
+
+ expect(component.$el.tagName).toEqual('TABLE');
+ });
+});
diff --git a/spec/javascripts/environments/environments_store_spec.js.es6 b/spec/javascripts/environments/environments_store_spec.js.es6
index 9a8300d3832..77e182b3830 100644
--- a/spec/javascripts/environments/environments_store_spec.js.es6
+++ b/spec/javascripts/environments/environments_store_spec.js.es6
@@ -1,70 +1,58 @@
-/* global environmentsList */
-
-require('~/environments/stores/environments_store');
-require('./mock_data');
+const Store = require('~/environments/stores/environments_store');
+const { environmentsList, serverData } = require('./mock_data');
(() => {
describe('Store', () => {
+ let store;
+
beforeEach(() => {
- gl.environmentsList.EnvironmentsStore.create();
+ store = new Store();
});
it('should start with a blank state', () => {
- expect(gl.environmentsList.EnvironmentsStore.state.environments.length).toBe(0);
- expect(gl.environmentsList.EnvironmentsStore.state.stoppedCounter).toBe(0);
- expect(gl.environmentsList.EnvironmentsStore.state.availableCounter).toBe(0);
+ expect(store.state.environments.length).toEqual(0);
+ expect(store.state.stoppedCounter).toEqual(0);
+ expect(store.state.availableCounter).toEqual(0);
+ expect(store.state.paginationInformation).toEqual({});
});
- describe('store environments', () => {
- beforeEach(() => {
- gl.environmentsList.EnvironmentsStore.storeEnvironments(environmentsList);
- });
-
- it('should count stopped environments and save the count in the state', () => {
- expect(gl.environmentsList.EnvironmentsStore.state.stoppedCounter).toBe(1);
- });
-
- it('should count available environments and save the count in the state', () => {
- expect(gl.environmentsList.EnvironmentsStore.state.availableCounter).toBe(3);
- });
-
- it('should store environments with same environment_type as sibilings', () => {
- expect(gl.environmentsList.EnvironmentsStore.state.environments.length).toBe(3);
-
- const parentFolder = gl.environmentsList.EnvironmentsStore.state.environments
- .filter(env => env.children && env.children.length > 0);
-
- expect(parentFolder[0].children.length).toBe(2);
- expect(parentFolder[0].children[0].environment_type).toBe('review');
- expect(parentFolder[0].children[1].environment_type).toBe('review');
- expect(parentFolder[0].children[0].name).toBe('test-environment');
- expect(parentFolder[0].children[1].name).toBe('test-environment-1');
- });
-
- it('should sort the environments alphabetically', () => {
- const { environments } = gl.environmentsList.EnvironmentsStore.state;
-
- expect(environments[0].name).toBe('production');
- expect(environments[1].name).toBe('review');
- expect(environments[1].children[0].name).toBe('test-environment');
- expect(environments[1].children[1].name).toBe('test-environment-1');
- expect(environments[2].name).toBe('review_app');
- });
+ it('should store environments', () => {
+ store.storeEnvironments(serverData);
+ expect(store.state.environments.length).toEqual(serverData.length);
+ expect(store.state.environments[0]).toEqual(environmentsList[0]);
});
- describe('toggleFolder', () => {
- beforeEach(() => {
- gl.environmentsList.EnvironmentsStore.storeEnvironments(environmentsList);
- });
-
- it('should toggle the open property for the given environment', () => {
- gl.environmentsList.EnvironmentsStore.toggleFolder('review');
+ it('should store available count', () => {
+ store.storeAvailableCount(2);
+ expect(store.state.availableCounter).toEqual(2);
+ });
- const { environments } = gl.environmentsList.EnvironmentsStore.state;
- const environment = environments.filter(env => env['vue-isChildren'] === true && env.name === 'review');
+ it('should store stopped count', () => {
+ store.storeStoppedCount(2);
+ expect(store.state.stoppedCounter).toEqual(2);
+ });
- expect(environment[0].isOpen).toBe(true);
- });
+ it('should store pagination information', () => {
+ const pagination = {
+ 'X-nExt-pAge': '2',
+ 'X-page': '1',
+ 'X-Per-Page': '1',
+ 'X-Prev-Page': '2',
+ 'X-TOTAL': '37',
+ 'X-Total-Pages': '2',
+ };
+
+ const expectedResult = {
+ perPage: 1,
+ page: 1,
+ total: 37,
+ totalPages: 2,
+ nextPage: 2,
+ previousPage: 2,
+ };
+
+ store.setPagination(pagination);
+ expect(store.state.paginationInformation).toEqual(expectedResult);
});
});
})();
diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js.es6 b/spec/javascripts/environments/folder/environments_folder_view_spec.js.es6
new file mode 100644
index 00000000000..d1335b5b304
--- /dev/null
+++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js.es6
@@ -0,0 +1,202 @@
+const Vue = require('vue');
+require('~/flash');
+const EnvironmentsFolderViewComponent = require('~/environments/folder/environments_folder_view');
+const { environmentsList } = require('../mock_data');
+
+describe('Environments Folder View', () => {
+ preloadFixtures('static/environments/environments_folder_view.html.raw');
+
+ beforeEach(() => {
+ loadFixtures('static/environments/environments_folder_view.html.raw');
+ window.history.pushState({}, null, 'environments/folders/build');
+ });
+
+ let component;
+
+ describe('successfull request', () => {
+ const environmentsResponseInterceptor = (request, next) => {
+ next(request.respondWith(JSON.stringify({
+ environments: environmentsList,
+ stopped_count: 1,
+ available_count: 0,
+ }), {
+ status: 200,
+ headers: {
+ 'X-nExt-pAge': '2',
+ 'x-page': '1',
+ 'X-Per-Page': '1',
+ 'X-Prev-Page': '',
+ 'X-TOTAL': '37',
+ 'X-Total-Pages': '2',
+ },
+ }));
+ };
+
+ beforeEach(() => {
+ Vue.http.interceptors.push(environmentsResponseInterceptor);
+ component = new EnvironmentsFolderViewComponent({
+ el: document.querySelector('#environments-folder-list-view'),
+ });
+ });
+
+ afterEach(() => {
+ Vue.http.interceptors = _.without(
+ Vue.http.interceptors, environmentsResponseInterceptor,
+ );
+ });
+
+ it('should render a table with environments', (done) => {
+ setTimeout(() => {
+ expect(
+ component.$el.querySelectorAll('table tbody tr').length,
+ ).toEqual(2);
+ done();
+ }, 0);
+ });
+
+ it('should render available tab with count', (done) => {
+ setTimeout(() => {
+ expect(
+ component.$el.querySelector('.js-available-environments-folder-tab').textContent,
+ ).toContain('Available');
+
+ expect(
+ component.$el.querySelector('.js-available-environments-folder-tab .js-available-environments-count').textContent,
+ ).toContain('0');
+ done();
+ }, 0);
+ });
+
+ it('should render stopped tab with count', (done) => {
+ setTimeout(() => {
+ expect(
+ component.$el.querySelector('.js-stopped-environments-folder-tab').textContent,
+ ).toContain('Stopped');
+
+ expect(
+ component.$el.querySelector('.js-stopped-environments-folder-tab .js-stopped-environments-count').textContent,
+ ).toContain('1');
+ done();
+ }, 0);
+ });
+
+ it('should render parent folder name', (done) => {
+ setTimeout(() => {
+ expect(
+ component.$el.querySelector('.js-folder-name').textContent,
+ ).toContain('Environments / build');
+ done();
+ }, 0);
+ });
+
+ describe('pagination', () => {
+ it('should render pagination', (done) => {
+ setTimeout(() => {
+ expect(
+ component.$el.querySelectorAll('.gl-pagination li').length,
+ ).toEqual(5);
+ done();
+ }, 0);
+ });
+
+ it('should update url when no search params are present', (done) => {
+ spyOn(gl.utils, 'visitUrl');
+ setTimeout(() => {
+ component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
+ expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2');
+ done();
+ }, 0);
+ });
+
+ it('should update url when page is already present', (done) => {
+ spyOn(gl.utils, 'visitUrl');
+ window.history.pushState({}, null, '?page=1');
+
+ setTimeout(() => {
+ component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
+ expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2');
+ done();
+ }, 0);
+ });
+
+ it('should update url when page and scope are already present', (done) => {
+ spyOn(gl.utils, 'visitUrl');
+ window.history.pushState({}, null, '?scope=all&page=1');
+
+ setTimeout(() => {
+ component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
+ expect(gl.utils.visitUrl).toHaveBeenCalledWith('?scope=all&page=2');
+ done();
+ }, 0);
+ });
+
+ it('should update url when page and scope are already present and page is first param', (done) => {
+ spyOn(gl.utils, 'visitUrl');
+ window.history.pushState({}, null, '?page=1&scope=all');
+
+ setTimeout(() => {
+ component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
+ expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2&scope=all');
+ done();
+ }, 0);
+ });
+ });
+ });
+
+ describe('unsuccessfull request', () => {
+ const environmentsErrorResponseInterceptor = (request, next) => {
+ next(request.respondWith(JSON.stringify([]), {
+ status: 500,
+ }));
+ };
+
+ beforeEach(() => {
+ Vue.http.interceptors.push(environmentsErrorResponseInterceptor);
+ });
+
+ afterEach(() => {
+ Vue.http.interceptors = _.without(
+ Vue.http.interceptors, environmentsErrorResponseInterceptor,
+ );
+ });
+
+ it('should not render a table', (done) => {
+ component = new EnvironmentsFolderViewComponent({
+ el: document.querySelector('#environments-folder-list-view'),
+ });
+
+ setTimeout(() => {
+ expect(
+ component.$el.querySelector('table'),
+ ).toBe(null);
+ done();
+ }, 0);
+ });
+
+ it('should render available tab with count 0', (done) => {
+ setTimeout(() => {
+ expect(
+ component.$el.querySelector('.js-available-environments-folder-tab').textContent,
+ ).toContain('Available');
+
+ expect(
+ component.$el.querySelector('.js-available-environments-folder-tab .js-available-environments-count').textContent,
+ ).toContain('0');
+ done();
+ }, 0);
+ });
+
+ it('should render stopped tab with count 0', (done) => {
+ setTimeout(() => {
+ expect(
+ component.$el.querySelector('.js-stopped-environments-folder-tab').textContent,
+ ).toContain('Stopped');
+
+ expect(
+ component.$el.querySelector('.js-stopped-environments-folder-tab .js-stopped-environments-count').textContent,
+ ).toContain('0');
+ done();
+ }, 0);
+ });
+ });
+});
diff --git a/spec/javascripts/environments/mock_data.js.es6 b/spec/javascripts/environments/mock_data.js.es6
index 80e1cbc6f4d..5c395c6b2d8 100644
--- a/spec/javascripts/environments/mock_data.js.es6
+++ b/spec/javascripts/environments/mock_data.js.es6
@@ -1,153 +1,92 @@
-
const environmentsList = [
{
- id: 31,
- name: 'production',
+ name: 'DEV',
+ size: 1,
+ id: 7,
state: 'available',
- external_url: 'https://www.gitlab.com',
- environment_type: null,
- last_deployment: {
- id: 64,
- iid: 5,
- sha: '500aabcb17c97bdcf2d0c410b70cb8556f0362dd',
- ref: {
- name: 'master',
- ref_url: 'http://localhost:3000/root/ci-folders/tree/master',
- },
- tag: false,
- 'last?': true,
- user: {
- name: 'Administrator',
- username: 'root',
- id: 1,
- state: 'active',
- avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
- web_url: 'http://localhost:3000/root',
- },
- commit: {
- id: '500aabcb17c97bdcf2d0c410b70cb8556f0362dd',
- short_id: '500aabcb',
- title: 'Update .gitlab-ci.yml',
- author_name: 'Administrator',
- author_email: 'admin@example.com',
- created_at: '2016-11-07T18:28:13.000+00:00',
- message: 'Update .gitlab-ci.yml',
- author: {
- name: 'Administrator',
- username: 'root',
- id: 1,
- state: 'active',
- avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
- web_url: 'http://localhost:3000/root',
- },
- commit_path: '/root/ci-folders/tree/500aabcb17c97bdcf2d0c410b70cb8556f0362dd',
- },
- deployable: {
- id: 1278,
- name: 'build',
- build_path: '/root/ci-folders/builds/1278',
- retry_path: '/root/ci-folders/builds/1278/retry',
- },
- manual_actions: [],
- },
- 'stop_action?': true,
- environment_path: '/root/ci-folders/environments/31',
- created_at: '2016-11-07T11:11:16.525Z',
- updated_at: '2016-11-07T11:11:16.525Z',
- },
- {
- id: 32,
- name: 'review_app',
- state: 'stopped',
- external_url: 'https://www.gitlab.com',
+ external_url: null,
environment_type: null,
- last_deployment: {
- id: 64,
- iid: 5,
- sha: '500aabcb17c97bdcf2d0c410b70cb8556f0362dd',
- ref: {
- name: 'master',
- ref_url: 'http://localhost:3000/root/ci-folders/tree/master',
- },
- tag: false,
- 'last?': true,
- user: {
- name: 'Administrator',
- username: 'root',
- id: 1,
- state: 'active',
- avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
- web_url: 'http://localhost:3000/root',
- },
- commit: {
- id: '500aabcb17c97bdcf2d0c410b70cb8556f0362dd',
- short_id: '500aabcb',
- title: 'Update .gitlab-ci.yml',
- author_name: 'Administrator',
- author_email: 'admin@example.com',
- created_at: '2016-11-07T18:28:13.000+00:00',
- message: 'Update .gitlab-ci.yml',
- author: {
- name: 'Administrator',
- username: 'root',
- id: 1,
- state: 'active',
- avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
- web_url: 'http://localhost:3000/root',
- },
- commit_path: '/root/ci-folders/tree/500aabcb17c97bdcf2d0c410b70cb8556f0362dd',
- },
- deployable: {
- id: 1278,
- name: 'build',
- build_path: '/root/ci-folders/builds/1278',
- retry_path: '/root/ci-folders/builds/1278/retry',
- },
- manual_actions: [],
- },
+ last_deployment: null,
'stop_action?': false,
- environment_path: '/root/ci-folders/environments/31',
- created_at: '2016-11-07T11:11:16.525Z',
- updated_at: '2016-11-07T11:11:16.525Z',
+ environment_path: '/root/review-app/environments/7',
+ stop_path: '/root/review-app/environments/7/stop',
+ created_at: '2017-01-31T10:53:46.894Z',
+ updated_at: '2017-01-31T10:53:46.894Z',
},
{
- id: 33,
- name: 'test-environment',
+ folderName: 'build',
+ size: 5,
+ id: 12,
+ name: 'build/update-README',
state: 'available',
- environment_type: 'review',
+ external_url: null,
+ environment_type: 'build',
last_deployment: null,
- 'stop_action?': true,
- environment_path: '/root/ci-folders/environments/31',
- created_at: '2016-11-07T11:11:16.525Z',
- updated_at: '2016-11-07T11:11:16.525Z',
+ 'stop_action?': false,
+ environment_path: '/root/review-app/environments/12',
+ stop_path: '/root/review-app/environments/12/stop',
+ created_at: '2017-02-01T19:42:18.400Z',
+ updated_at: '2017-02-01T19:42:18.400Z',
},
+];
+
+const serverData = [
{
- id: 34,
- name: 'test-environment-1',
- state: 'available',
- environment_type: 'review',
- last_deployment: null,
- 'stop_action?': true,
- environment_path: '/root/ci-folders/environments/31',
- created_at: '2016-11-07T11:11:16.525Z',
- updated_at: '2016-11-07T11:11:16.525Z',
+ name: 'DEV',
+ size: 1,
+ latest: {
+ id: 7,
+ name: 'DEV',
+ state: 'available',
+ external_url: null,
+ environment_type: null,
+ last_deployment: null,
+ 'stop_action?': false,
+ environment_path: '/root/review-app/environments/7',
+ stop_path: '/root/review-app/environments/7/stop',
+ created_at: '2017-01-31T10:53:46.894Z',
+ updated_at: '2017-01-31T10:53:46.894Z',
+ },
+ },
+ {
+ name: 'build',
+ size: 5,
+ latest: {
+ id: 12,
+ name: 'build/update-README',
+ state: 'available',
+ external_url: null,
+ environment_type: 'build',
+ last_deployment: null,
+ 'stop_action?': false,
+ environment_path: '/root/review-app/environments/12',
+ stop_path: '/root/review-app/environments/12/stop',
+ created_at: '2017-02-01T19:42:18.400Z',
+ updated_at: '2017-02-01T19:42:18.400Z',
+ },
},
];
-window.environmentsList = environmentsList;
-
const environment = {
- id: 4,
- name: 'production',
- state: 'available',
- external_url: 'http://production.',
- environment_type: null,
- last_deployment: {},
- 'stop_action?': false,
- environment_path: '/root/review-app/environments/4',
- stop_path: '/root/review-app/environments/4/stop',
- created_at: '2016-12-16T11:51:04.690Z',
- updated_at: '2016-12-16T12:04:51.133Z',
+ name: 'DEV',
+ size: 1,
+ latest: {
+ id: 7,
+ name: 'DEV',
+ state: 'available',
+ external_url: null,
+ environment_type: null,
+ last_deployment: null,
+ 'stop_action?': false,
+ environment_path: '/root/review-app/environments/7',
+ stop_path: '/root/review-app/environments/7/stop',
+ created_at: '2017-01-31T10:53:46.894Z',
+ updated_at: '2017-01-31T10:53:46.894Z',
+ },
};
-window.environment = environment;
+module.exports = {
+ environmentsList,
+ environment,
+ serverData,
+};
diff --git a/spec/javascripts/extensions/array_spec.js.es6 b/spec/javascripts/extensions/array_spec.js.es6
index ba5eb81defc..60f6b9b78e3 100644
--- a/spec/javascripts/extensions/array_spec.js.es6
+++ b/spec/javascripts/extensions/array_spec.js.es6
@@ -42,4 +42,4 @@ require('~/extensions/array');
});
});
});
-}).call(this);
+}).call(window);
diff --git a/spec/javascripts/extensions/jquery_spec.js b/spec/javascripts/extensions/jquery_spec.js
index c0bb0419814..096d3272eac 100644
--- a/spec/javascripts/extensions/jquery_spec.js
+++ b/spec/javascripts/extensions/jquery_spec.js
@@ -39,4 +39,4 @@ require('~/extensions/jquery');
});
});
});
-}).call(this);
+}).call(window);
diff --git a/spec/javascripts/filtered_search/dropdown_user_spec.js.es6 b/spec/javascripts/filtered_search/dropdown_user_spec.js.es6
index f4b0d60db34..fa9d03c8a9a 100644
--- a/spec/javascripts/filtered_search/dropdown_user_spec.js.es6
+++ b/spec/javascripts/filtered_search/dropdown_user_spec.js.es6
@@ -9,7 +9,7 @@ require('~/filtered_search/dropdown_user');
let dropdownUser;
beforeEach(() => {
- spyOn(gl.FilteredSearchDropdown.prototype, 'constructor').and.callFake(() => {});
+ spyOn(gl.DropdownUser.prototype, 'bindEvents').and.callFake(() => {});
spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {});
spyOn(gl.DropdownUtils, 'getSearchInput').and.callFake(() => {});
@@ -39,7 +39,7 @@ require('~/filtered_search/dropdown_user');
describe('config droplabAjaxFilter\'s endpoint', () => {
beforeEach(() => {
- spyOn(gl.FilteredSearchDropdown.prototype, 'constructor').and.callFake(() => {});
+ spyOn(gl.DropdownUser.prototype, 'bindEvents').and.callFake(() => {});
spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {});
});
diff --git a/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js.es6 b/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js.es6
index 84c0e9cbfe2..a91801cfc89 100644
--- a/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js.es6
+++ b/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js.es6
@@ -99,6 +99,29 @@ require('~/filtered_search/filtered_search_tokenizer');
expect(results.tokens[2].value).toBe('Doing');
expect(results.tokens[2].symbol).toBe('~');
});
+
+ it('returns search value for invalid tokens', () => {
+ const results = gl.FilteredSearchTokenizer.processTokens('fake:token');
+ expect(results.lastToken).toBe('fake:token');
+ expect(results.searchToken).toBe('fake:token');
+ expect(results.tokens.length).toEqual(0);
+ });
+
+ it('returns search value and token for mix of valid and invalid tokens', () => {
+ const results = gl.FilteredSearchTokenizer.processTokens('label:real fake:token');
+ expect(results.tokens.length).toEqual(1);
+ expect(results.tokens[0].key).toBe('label');
+ expect(results.tokens[0].value).toBe('real');
+ expect(results.tokens[0].symbol).toBe('');
+ expect(results.lastToken).toBe('fake:token');
+ expect(results.searchToken).toBe('fake:token');
+ });
+
+ it('returns search value for invalid symbols', () => {
+ const results = gl.FilteredSearchTokenizer.processTokens('std::includes');
+ expect(results.lastToken).toBe('std::includes');
+ expect(results.searchToken).toBe('std::includes');
+ });
});
});
})();
diff --git a/spec/javascripts/fixtures/.gitignore b/spec/javascripts/fixtures/.gitignore
index 009b68d5d1c..0c35cdd778e 100644
--- a/spec/javascripts/fixtures/.gitignore
+++ b/spec/javascripts/fixtures/.gitignore
@@ -1 +1,2 @@
*.html.raw
+*.json
diff --git a/spec/javascripts/fixtures/ajax_loading_spinner.html.haml b/spec/javascripts/fixtures/ajax_loading_spinner.html.haml
new file mode 100644
index 00000000000..09d8c9df3b2
--- /dev/null
+++ b/spec/javascripts/fixtures/ajax_loading_spinner.html.haml
@@ -0,0 +1,2 @@
+%a.js-ajax-loading-spinner{href: "http://goesnowhere.nothing/whereami", data: {remote: true}}
+ %i.fa.fa-trash-o
diff --git a/spec/javascripts/fixtures/behaviors/quick_submit.html.haml b/spec/javascripts/fixtures/behaviors/quick_submit.html.haml
deleted file mode 100644
index dc2ceed42f4..00000000000
--- a/spec/javascripts/fixtures/behaviors/quick_submit.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-%form.js-quick-submit{ action: '/foo' }
- %input{ type: 'text', class: 'quick-submit-input'}
- %textarea
-
- %input{ type: 'submit'} Submit
- %button.btn{ type: 'submit' } Submit
diff --git a/spec/javascripts/fixtures/behaviors/requires_input.html.haml b/spec/javascripts/fixtures/behaviors/requires_input.html.haml
deleted file mode 100644
index c3f905e912e..00000000000
--- a/spec/javascripts/fixtures/behaviors/requires_input.html.haml
+++ /dev/null
@@ -1,18 +0,0 @@
-%form.js-requires-input
- %input{type: 'text', id: 'required1', required: 'required'}
- %input{type: 'text', id: 'required2', required: 'required'}
- %input{type: 'text', id: 'required3', required: 'required', value: 'Pre-filled'}
- %input{type: 'text', id: 'optional1'}
-
- %textarea{id: 'required4', required: 'required'}
- %textarea{id: 'optional2'}
-
- %select{id: 'required5', required: 'required'}
- %option Zero
- %option{value: '1'} One
- %select{id: 'optional3', required: 'required'}
- %option Zero
- %option{value: '1'} One
-
- %button.submit{type: 'submit', value: 'Submit'}
- %input.submit{type: 'submit', value: 'Submit'}
diff --git a/spec/javascripts/fixtures/branches.rb b/spec/javascripts/fixtures/branches.rb
new file mode 100644
index 00000000000..a059818145b
--- /dev/null
+++ b/spec/javascripts/fixtures/branches.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+describe Projects::BranchesController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project, :repository, namespace: namespace, path: 'branches-project') }
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('branches/')
+ end
+
+ before(:each) do
+ sign_in(admin)
+ end
+
+ it 'branches/new_branch.html.raw' do |example|
+ get :new,
+ namespace_id: project.namespace.to_param,
+ project_id: project
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+end
diff --git a/spec/javascripts/fixtures/builds.rb b/spec/javascripts/fixtures/builds.rb
index 978e25a1c32..320de791b08 100644
--- a/spec/javascripts/fixtures/builds.rb
+++ b/spec/javascripts/fixtures/builds.rb
@@ -24,7 +24,7 @@ describe Projects::BuildsController, '(JavaScript fixtures)', type: :controller
it 'builds/build-with-artifacts.html.raw' do |example|
get :show,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: build_with_artifacts.to_param
expect(response).to be_success
diff --git a/spec/javascripts/fixtures/dashboard.html.haml b/spec/javascripts/fixtures/dashboard.html.haml
deleted file mode 100644
index 32446acfd60..00000000000
--- a/spec/javascripts/fixtures/dashboard.html.haml
+++ /dev/null
@@ -1,45 +0,0 @@
-%ul.nav.nav-sidebar
- %li.home.active
- %a.dashboard-shortcuts-projects
- %span
- Projects
- %li
- %a
- %span
- Todos
- %span.count.js-todos-count
- 1
- %li
- %a.dashboard-shortcuts-activity
- %span
- Activity
- %li
- %a
- %span
- Groups
- %li
- %a
- %span
- Milestones
- %li
- %a.dashboard-shortcuts-issues
- %span
- Issues
- %span
- 1
- %li
- %a.dashboard-shortcuts-merge_requests
- %span
- Merge Requests
- %li
- %a
- %span
- Snippets
- %li
- %a
- %span
- Help
- %li
- %a
- %span
- Profile Settings
diff --git a/spec/javascripts/fixtures/emoji_menu.js b/spec/javascripts/fixtures/emoji_menu.js
index 2ef242901e8..a50812d9517 100644
--- a/spec/javascripts/fixtures/emoji_menu.js
+++ b/spec/javascripts/fixtures/emoji_menu.js
@@ -1,4 +1,4 @@
/* eslint-disable space-before-function-paren */
(function() {
window.emojiMenu = "<div class='emoji-menu'>\n <input type=\"text\" name=\"emoji_search\" id=\"emoji_search\" value=\"\" class=\"emoji-search search-input form-control\" />\n <div class='emoji-menu-content'>\n <h5 class='emoji-menu-title'>\n Emoticons\n </h5>\n <ul class='clearfix emoji-menu-list'>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F47D\" title=\"alien\" data-aliases=\"\" data-emoji=\"alien\" data-unicode-name=\"1F47D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F47C\" title=\"angel\" data-aliases=\"\" data-emoji=\"angel\" data-unicode-name=\"1F47C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A2\" title=\"anger\" data-aliases=\"\" data-emoji=\"anger\" data-unicode-name=\"1F4A2\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F620\" title=\"angry\" data-aliases=\"\" data-emoji=\"angry\" data-unicode-name=\"1F620\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F627\" title=\"anguished\" data-aliases=\"\" data-emoji=\"anguished\" data-unicode-name=\"1F627\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F632\" title=\"astonished\" data-aliases=\"\" data-emoji=\"astonished\" data-unicode-name=\"1F632\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45F\" title=\"athletic_shoe\" data-aliases=\"\" data-emoji=\"athletic_shoe\" data-unicode-name=\"1F45F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F476\" title=\"baby\" data-aliases=\"\" data-emoji=\"baby\" data-unicode-name=\"1F476\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F459\" title=\"bikini\" data-aliases=\"\" data-emoji=\"bikini\" data-unicode-name=\"1F459\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F499\" title=\"blue_heart\" data-aliases=\"\" data-emoji=\"blue_heart\" data-unicode-name=\"1F499\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60A\" title=\"blush\" data-aliases=\"\" data-emoji=\"blush\" data-unicode-name=\"1F60A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A5\" title=\"boom\" data-aliases=\"\" data-emoji=\"boom\" data-unicode-name=\"1F4A5\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F462\" title=\"boot\" data-aliases=\"\" data-emoji=\"boot\" data-unicode-name=\"1F462\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F647\" title=\"bow\" data-aliases=\"\" data-emoji=\"bow\" data-unicode-name=\"1F647\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F466\" title=\"boy\" data-aliases=\"\" data-emoji=\"boy\" data-unicode-name=\"1F466\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F470\" title=\"bride_with_veil\" data-aliases=\"\" data-emoji=\"bride_with_veil\" data-unicode-name=\"1F470\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4BC\" title=\"briefcase\" data-aliases=\"\" data-emoji=\"briefcase\" data-unicode-name=\"1F4BC\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F494\" title=\"broken_heart\" data-aliases=\"\" data-emoji=\"broken_heart\" data-unicode-name=\"1F494\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F464\" title=\"bust_in_silhouette\" data-aliases=\"\" data-emoji=\"bust_in_silhouette\" data-unicode-name=\"1F464\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F465\" title=\"busts_in_silhouette\" data-aliases=\"\" data-emoji=\"busts_in_silhouette\" data-unicode-name=\"1F465\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44F\" title=\"clap\" data-aliases=\"\" data-emoji=\"clap\" data-unicode-name=\"1F44F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F302\" title=\"closed_umbrella\" data-aliases=\"\" data-emoji=\"closed_umbrella\" data-unicode-name=\"1F302\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F630\" title=\"cold_sweat\" data-aliases=\"\" data-emoji=\"cold_sweat\" data-unicode-name=\"1F630\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F616\" title=\"confounded\" data-aliases=\"\" data-emoji=\"confounded\" data-unicode-name=\"1F616\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F615\" title=\"confused\" data-aliases=\"\" data-emoji=\"confused\" data-unicode-name=\"1F615\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F477\" title=\"construction_worker\" data-aliases=\"\" data-emoji=\"construction_worker\" data-unicode-name=\"1F477\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46E\" title=\"cop\" data-aliases=\"\" data-emoji=\"cop\" data-unicode-name=\"1F46E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46B\" title=\"couple\" data-aliases=\"\" data-emoji=\"couple\" data-unicode-name=\"1F46B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F491\" title=\"couple_with_heart\" data-aliases=\"\" data-emoji=\"couple_with_heart\" data-unicode-name=\"1F491\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48F\" title=\"couplekiss\" data-aliases=\"\" data-emoji=\"couplekiss\" data-unicode-name=\"1F48F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F451\" title=\"crown\" data-aliases=\"\" data-emoji=\"crown\" data-unicode-name=\"1F451\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F622\" title=\"cry\" data-aliases=\"\" data-emoji=\"cry\" data-unicode-name=\"1F622\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63F\" title=\"crying_cat_face\" data-aliases=\"\" data-emoji=\"crying_cat_face\" data-unicode-name=\"1F63F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F498\" title=\"cupid\" data-aliases=\"\" data-emoji=\"cupid\" data-unicode-name=\"1F498\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F483\" title=\"dancer\" data-aliases=\"\" data-emoji=\"dancer\" data-unicode-name=\"1F483\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46F\" title=\"dancers\" data-aliases=\"\" data-emoji=\"dancers\" data-unicode-name=\"1F46F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A8\" title=\"dash\" data-aliases=\"\" data-emoji=\"dash\" data-unicode-name=\"1F4A8\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61E\" title=\"disappointed\" data-aliases=\"\" data-emoji=\"disappointed\" data-unicode-name=\"1F61E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F625\" title=\"disappointed_relieved\" data-aliases=\"\" data-emoji=\"disappointed_relieved\" data-unicode-name=\"1F625\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4AB\" title=\"dizzy\" data-aliases=\"\" data-emoji=\"dizzy\" data-unicode-name=\"1F4AB\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F635\" title=\"dizzy_face\" data-aliases=\"\" data-emoji=\"dizzy_face\" data-unicode-name=\"1F635\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F457\" title=\"dress\" data-aliases=\"\" data-emoji=\"dress\" data-unicode-name=\"1F457\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A7\" title=\"droplet\" data-aliases=\"\" data-emoji=\"droplet\" data-unicode-name=\"1F4A7\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F442\" title=\"ear\" data-aliases=\"\" data-emoji=\"ear\" data-unicode-name=\"1F442\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F611\" title=\"expressionless\" data-aliases=\"\" data-emoji=\"expressionless\" data-unicode-name=\"1F611\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F453\" title=\"eyeglasses\" data-aliases=\"\" data-emoji=\"eyeglasses\" data-unicode-name=\"1F453\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F440\" title=\"eyes\" data-aliases=\"\" data-emoji=\"eyes\" data-unicode-name=\"1F440\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46A\" title=\"family\" data-aliases=\"\" data-emoji=\"family\" data-unicode-name=\"1F46A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F628\" title=\"fearful\" data-aliases=\"\" data-emoji=\"fearful\" data-unicode-name=\"1F628\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F525\" title=\"fire\" data-aliases=\":flame:\" data-emoji=\"fire\" data-unicode-name=\"1F525\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-270A\" title=\"fist\" data-aliases=\"\" data-emoji=\"fist\" data-unicode-name=\"270A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F633\" title=\"flushed\" data-aliases=\"\" data-emoji=\"flushed\" data-unicode-name=\"1F633\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F463\" title=\"footprints\" data-aliases=\"\" data-emoji=\"footprints\" data-unicode-name=\"1F463\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F626\" title=\"frowning\" data-aliases=\":anguished:\" data-emoji=\"frowning\" data-unicode-name=\"1F626\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48E\" title=\"gem\" data-aliases=\"\" data-emoji=\"gem\" data-unicode-name=\"1F48E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F467\" title=\"girl\" data-aliases=\"\" data-emoji=\"girl\" data-unicode-name=\"1F467\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F49A\" title=\"green_heart\" data-aliases=\"\" data-emoji=\"green_heart\" data-unicode-name=\"1F49A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62C\" title=\"grimacing\" data-aliases=\"\" data-emoji=\"grimacing\" data-unicode-name=\"1F62C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F601\" title=\"grin\" data-aliases=\"\" data-emoji=\"grin\" data-unicode-name=\"1F601\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F600\" title=\"grinning\" data-aliases=\"\" data-emoji=\"grinning\" data-unicode-name=\"1F600\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F482\" title=\"guardsman\" data-aliases=\"\" data-emoji=\"guardsman\" data-unicode-name=\"1F482\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F487\" title=\"haircut\" data-aliases=\"\" data-emoji=\"haircut\" data-unicode-name=\"1F487\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45C\" title=\"handbag\" data-aliases=\"\" data-emoji=\"handbag\" data-unicode-name=\"1F45C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F649\" title=\"hear_no_evil\" data-aliases=\"\" data-emoji=\"hear_no_evil\" data-unicode-name=\"1F649\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-2764\" title=\"heart\" data-aliases=\"\" data-emoji=\"heart\" data-unicode-name=\"2764\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60D\" title=\"heart_eyes\" data-aliases=\"\" data-emoji=\"heart_eyes\" data-unicode-name=\"1F60D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63B\" title=\"heart_eyes_cat\" data-aliases=\"\" data-emoji=\"heart_eyes_cat\" data-unicode-name=\"1F63B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F493\" title=\"heartbeat\" data-aliases=\"\" data-emoji=\"heartbeat\" data-unicode-name=\"1F493\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F497\" title=\"heartpulse\" data-aliases=\"\" data-emoji=\"heartpulse\" data-unicode-name=\"1F497\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F460\" title=\"high_heel\" data-aliases=\"\" data-emoji=\"high_heel\" data-unicode-name=\"1F460\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62F\" title=\"hushed\" data-aliases=\"\" data-emoji=\"hushed\" data-unicode-name=\"1F62F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F47F\" title=\"imp\" data-aliases=\"\" data-emoji=\"imp\" data-unicode-name=\"1F47F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F481\" title=\"information_desk_person\" data-aliases=\"\" data-emoji=\"information_desk_person\" data-unicode-name=\"1F481\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F607\" title=\"innocent\" data-aliases=\"\" data-emoji=\"innocent\" data-unicode-name=\"1F607\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F47A\" title=\"japanese_goblin\" data-aliases=\"\" data-emoji=\"japanese_goblin\" data-unicode-name=\"1F47A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F479\" title=\"japanese_ogre\" data-aliases=\"\" data-emoji=\"japanese_ogre\" data-unicode-name=\"1F479\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F456\" title=\"jeans\" data-aliases=\"\" data-emoji=\"jeans\" data-unicode-name=\"1F456\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F602\" title=\"joy\" data-aliases=\"\" data-emoji=\"joy\" data-unicode-name=\"1F602\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F639\" title=\"joy_cat\" data-aliases=\"\" data-emoji=\"joy_cat\" data-unicode-name=\"1F639\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F458\" title=\"kimono\" data-aliases=\"\" data-emoji=\"kimono\" data-unicode-name=\"1F458\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48B\" title=\"kiss\" data-aliases=\"\" data-emoji=\"kiss\" data-unicode-name=\"1F48B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F617\" title=\"kissing\" data-aliases=\"\" data-emoji=\"kissing\" data-unicode-name=\"1F617\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63D\" title=\"kissing_cat\" data-aliases=\"\" data-emoji=\"kissing_cat\" data-unicode-name=\"1F63D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61A\" title=\"kissing_closed_eyes\" data-aliases=\"\" data-emoji=\"kissing_closed_eyes\" data-unicode-name=\"1F61A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F618\" title=\"kissing_heart\" data-aliases=\"\" data-emoji=\"kissing_heart\" data-unicode-name=\"1F618\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F619\" title=\"kissing_smiling_eyes\" data-aliases=\"\" data-emoji=\"kissing_smiling_eyes\" data-unicode-name=\"1F619\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F606\" title=\"laughing\" data-aliases=\":satisfied:\" data-emoji=\"laughing\" data-unicode-name=\"1F606\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F444\" title=\"lips\" data-aliases=\"\" data-emoji=\"lips\" data-unicode-name=\"1F444\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F484\" title=\"lipstick\" data-aliases=\"\" data-emoji=\"lipstick\" data-unicode-name=\"1F484\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48C\" title=\"love_letter\" data-aliases=\"\" data-emoji=\"love_letter\" data-unicode-name=\"1F48C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F468\" title=\"man\" data-aliases=\"\" data-emoji=\"man\" data-unicode-name=\"1F468\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F472\" title=\"man_with_gua_pi_mao\" data-aliases=\"\" data-emoji=\"man_with_gua_pi_mao\" data-unicode-name=\"1F472\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F473\" title=\"man_with_turban\" data-aliases=\"\" data-emoji=\"man_with_turban\" data-unicode-name=\"1F473\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45E\" title=\"mans_shoe\" data-aliases=\"\" data-emoji=\"mans_shoe\" data-unicode-name=\"1F45E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F637\" title=\"mask\" data-aliases=\"\" data-emoji=\"mask\" data-unicode-name=\"1F637\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F486\" title=\"massage\" data-aliases=\"\" data-emoji=\"massage\" data-unicode-name=\"1F486\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4AA\" title=\"muscle\" data-aliases=\"\" data-emoji=\"muscle\" data-unicode-name=\"1F4AA\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F485\" title=\"nail_care\" data-aliases=\"\" data-emoji=\"nail_care\" data-unicode-name=\"1F485\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F454\" title=\"necktie\" data-aliases=\"\" data-emoji=\"necktie\" data-unicode-name=\"1F454\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F610\" title=\"neutral_face\" data-aliases=\"\" data-emoji=\"neutral_face\" data-unicode-name=\"1F610\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F645\" title=\"no_good\" data-aliases=\"\" data-emoji=\"no_good\" data-unicode-name=\"1F645\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F636\" title=\"no_mouth\" data-aliases=\"\" data-emoji=\"no_mouth\" data-unicode-name=\"1F636\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F443\" title=\"nose\" data-aliases=\"\" data-emoji=\"nose\" data-unicode-name=\"1F443\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44C\" title=\"ok_hand\" data-aliases=\"\" data-emoji=\"ok_hand\" data-unicode-name=\"1F44C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F646\" title=\"ok_woman\" data-aliases=\"\" data-emoji=\"ok_woman\" data-unicode-name=\"1F646\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F474\" title=\"older_man\" data-aliases=\"\" data-emoji=\"older_man\" data-unicode-name=\"1F474\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F475\" title=\"older_woman\" data-aliases=\":grandma:\" data-emoji=\"older_woman\" data-unicode-name=\"1F475\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F450\" title=\"open_hands\" data-aliases=\"\" data-emoji=\"open_hands\" data-unicode-name=\"1F450\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62E\" title=\"open_mouth\" data-aliases=\"\" data-emoji=\"open_mouth\" data-unicode-name=\"1F62E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F614\" title=\"pensive\" data-aliases=\"\" data-emoji=\"pensive\" data-unicode-name=\"1F614\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F623\" title=\"persevere\" data-aliases=\"\" data-emoji=\"persevere\" data-unicode-name=\"1F623\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64D\" title=\"person_frowning\" data-aliases=\"\" data-emoji=\"person_frowning\" data-unicode-name=\"1F64D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F471\" title=\"person_with_blond_hair\" data-aliases=\"\" data-emoji=\"person_with_blond_hair\" data-unicode-name=\"1F471\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64E\" title=\"person_with_pouting_face\" data-aliases=\"\" data-emoji=\"person_with_pouting_face\" data-unicode-name=\"1F64E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F447\" title=\"point_down\" data-aliases=\"\" data-emoji=\"point_down\" data-unicode-name=\"1F447\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F448\" title=\"point_left\" data-aliases=\"\" data-emoji=\"point_left\" data-unicode-name=\"1F448\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F449\" title=\"point_right\" data-aliases=\"\" data-emoji=\"point_right\" data-unicode-name=\"1F449\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-261D\" title=\"point_up\" data-aliases=\"\" data-emoji=\"point_up\" data-unicode-name=\"261D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F446\" title=\"point_up_2\" data-aliases=\"\" data-emoji=\"point_up_2\" data-unicode-name=\"1F446\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A9\" title=\"poop\" data-aliases=\":shit: :hankey: :poo:\" data-emoji=\"poop\" data-unicode-name=\"1F4A9\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45D\" title=\"pouch\" data-aliases=\"\" data-emoji=\"pouch\" data-unicode-name=\"1F45D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63E\" title=\"pouting_cat\" data-aliases=\"\" data-emoji=\"pouting_cat\" data-unicode-name=\"1F63E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64F\" title=\"pray\" data-aliases=\"\" data-emoji=\"pray\" data-unicode-name=\"1F64F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F478\" title=\"princess\" data-aliases=\"\" data-emoji=\"princess\" data-unicode-name=\"1F478\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44A\" title=\"punch\" data-aliases=\"\" data-emoji=\"punch\" data-unicode-name=\"1F44A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F49C\" title=\"purple_heart\" data-aliases=\"\" data-emoji=\"purple_heart\" data-unicode-name=\"1F49C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45B\" title=\"purse\" data-aliases=\"\" data-emoji=\"purse\" data-unicode-name=\"1F45B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F621\" title=\"rage\" data-aliases=\"\" data-emoji=\"rage\" data-unicode-name=\"1F621\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-270B\" title=\"raised_hand\" data-aliases=\"\" data-emoji=\"raised_hand\" data-unicode-name=\"270B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64C\" title=\"raised_hands\" data-aliases=\"\" data-emoji=\"raised_hands\" data-unicode-name=\"1F64C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64B\" title=\"raising_hand\" data-aliases=\"\" data-emoji=\"raising_hand\" data-unicode-name=\"1F64B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-263A\" title=\"relaxed\" data-aliases=\"\" data-emoji=\"relaxed\" data-unicode-name=\"263A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60C\" title=\"relieved\" data-aliases=\"\" data-emoji=\"relieved\" data-unicode-name=\"1F60C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F49E\" title=\"revolving_hearts\" data-aliases=\"\" data-emoji=\"revolving_hearts\" data-unicode-name=\"1F49E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F380\" title=\"ribbon\" data-aliases=\"\" data-emoji=\"ribbon\" data-unicode-name=\"1F380\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48D\" title=\"ring\" data-aliases=\"\" data-emoji=\"ring\" data-unicode-name=\"1F48D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F3C3\" title=\"runner\" data-aliases=\"\" data-emoji=\"runner\" data-unicode-name=\"1F3C3\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F3BD\" title=\"running_shirt_with_sash\" data-aliases=\"\" data-emoji=\"running_shirt_with_sash\" data-unicode-name=\"1F3BD\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F461\" title=\"sandal\" data-aliases=\"\" data-emoji=\"sandal\" data-unicode-name=\"1F461\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F631\" title=\"scream\" data-aliases=\"\" data-emoji=\"scream\" data-unicode-name=\"1F631\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F640\" title=\"scream_cat\" data-aliases=\"\" data-emoji=\"scream_cat\" data-unicode-name=\"1F640\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F648\" title=\"see_no_evil\" data-aliases=\"\" data-emoji=\"see_no_evil\" data-unicode-name=\"1F648\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F455\" title=\"shirt\" data-aliases=\"\" data-emoji=\"shirt\" data-unicode-name=\"1F455\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F480\" title=\"skull\" data-aliases=\":skeleton:\" data-emoji=\"skull\" data-unicode-name=\"1F480\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F634\" title=\"sleeping\" data-aliases=\"\" data-emoji=\"sleeping\" data-unicode-name=\"1F634\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62A\" title=\"sleepy\" data-aliases=\"\" data-emoji=\"sleepy\" data-unicode-name=\"1F62A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F604\" title=\"smile\" data-aliases=\"\" data-emoji=\"smile\" data-unicode-name=\"1F604\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F638\" title=\"smile_cat\" data-aliases=\"\" data-emoji=\"smile_cat\" data-unicode-name=\"1F638\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F603\" title=\"smiley\" data-aliases=\"\" data-emoji=\"smiley\" data-unicode-name=\"1F603\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63A\" title=\"smiley_cat\" data-aliases=\"\" data-emoji=\"smiley_cat\" data-unicode-name=\"1F63A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F608\" title=\"smiling_imp\" data-aliases=\"\" data-emoji=\"smiling_imp\" data-unicode-name=\"1F608\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60F\" title=\"smirk\" data-aliases=\"\" data-emoji=\"smirk\" data-unicode-name=\"1F60F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63C\" title=\"smirk_cat\" data-aliases=\"\" data-emoji=\"smirk_cat\" data-unicode-name=\"1F63C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62D\" title=\"sob\" data-aliases=\"\" data-emoji=\"sob\" data-unicode-name=\"1F62D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-2728\" title=\"sparkles\" data-aliases=\"\" data-emoji=\"sparkles\" data-unicode-name=\"2728\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F496\" title=\"sparkling_heart\" data-aliases=\"\" data-emoji=\"sparkling_heart\" data-unicode-name=\"1F496\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64A\" title=\"speak_no_evil\" data-aliases=\"\" data-emoji=\"speak_no_evil\" data-unicode-name=\"1F64A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4AC\" title=\"speech_balloon\" data-aliases=\"\" data-emoji=\"speech_balloon\" data-unicode-name=\"1F4AC\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F31F\" title=\"star2\" data-aliases=\"\" data-emoji=\"star2\" data-unicode-name=\"1F31F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61B\" title=\"stuck_out_tongue\" data-aliases=\"\" data-emoji=\"stuck_out_tongue\" data-unicode-name=\"1F61B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61D\" title=\"stuck_out_tongue_closed_eyes\" data-aliases=\"\" data-emoji=\"stuck_out_tongue_closed_eyes\" data-unicode-name=\"1F61D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61C\" title=\"stuck_out_tongue_winking_eye\" data-aliases=\"\" data-emoji=\"stuck_out_tongue_winking_eye\" data-unicode-name=\"1F61C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60E\" title=\"sunglasses\" data-aliases=\"\" data-emoji=\"sunglasses\" data-unicode-name=\"1F60E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F613\" title=\"sweat\" data-aliases=\"\" data-emoji=\"sweat\" data-unicode-name=\"1F613\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A6\" title=\"sweat_drops\" data-aliases=\"\" data-emoji=\"sweat_drops\" data-unicode-name=\"1F4A6\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F605\" title=\"sweat_smile\" data-aliases=\"\" data-emoji=\"sweat_smile\" data-unicode-name=\"1F605\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4AD\" title=\"thought_balloon\" data-aliases=\"\" data-emoji=\"thought_balloon\" data-unicode-name=\"1F4AD\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44E\" title=\"thumbsdown\" data-aliases=\":-1:\" data-emoji=\"thumbsdown\" data-unicode-name=\"1F44E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44D\" title=\"thumbsup\" data-aliases=\":+1:\" data-emoji=\"thumbsup\" data-unicode-name=\"1F44D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62B\" title=\"tired_face\" data-aliases=\"\" data-emoji=\"tired_face\" data-unicode-name=\"1F62B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F445\" title=\"tongue\" data-aliases=\"\" data-emoji=\"tongue\" data-unicode-name=\"1F445\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F3A9\" title=\"tophat\" data-aliases=\"\" data-emoji=\"tophat\" data-unicode-name=\"1F3A9\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F624\" title=\"triumph\" data-aliases=\"\" data-emoji=\"triumph\" data-unicode-name=\"1F624\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F495\" title=\"two_hearts\" data-aliases=\"\" data-emoji=\"two_hearts\" data-unicode-name=\"1F495\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46C\" title=\"two_men_holding_hands\" data-aliases=\"\" data-emoji=\"two_men_holding_hands\" data-unicode-name=\"1F46C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46D\" title=\"two_women_holding_hands\" data-aliases=\"\" data-emoji=\"two_women_holding_hands\" data-unicode-name=\"1F46D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F612\" title=\"unamused\" data-aliases=\"\" data-emoji=\"unamused\" data-unicode-name=\"1F612\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-270C\" title=\"v\" data-aliases=\"\" data-emoji=\"v\" data-unicode-name=\"270C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F6B6\" title=\"walking\" data-aliases=\"\" data-emoji=\"walking\" data-unicode-name=\"1F6B6\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44B\" title=\"wave\" data-aliases=\"\" data-emoji=\"wave\" data-unicode-name=\"1F44B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F629\" title=\"weary\" data-aliases=\"\" data-emoji=\"weary\" data-unicode-name=\"1F629\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F609\" title=\"wink\" data-aliases=\"\" data-emoji=\"wink\" data-unicode-name=\"1F609\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F469\" title=\"woman\" data-aliases=\"\" data-emoji=\"woman\" data-unicode-name=\"1F469\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45A\" title=\"womans_clothes\" data-aliases=\"\" data-emoji=\"womans_clothes\" data-unicode-name=\"1F45A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F452\" title=\"womans_hat\" data-aliases=\"\" data-emoji=\"womans_hat\" data-unicode-name=\"1F452\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61F\" title=\"worried\" data-aliases=\"\" data-emoji=\"worried\" data-unicode-name=\"1F61F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F49B\" title=\"yellow_heart\" data-aliases=\"\" data-emoji=\"yellow_heart\" data-unicode-name=\"1F49B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60B\" title=\"yum\" data-aliases=\"\" data-emoji=\"yum\" data-unicode-name=\"1F60B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A4\" title=\"zzz\" data-aliases=\"\" data-emoji=\"zzz\" data-unicode-name=\"1F4A4\"></div>\n </button>\n </li>\n </ul>\n </div>\n</div>";
-}).call(this);
+}).call(window);
diff --git a/spec/javascripts/fixtures/environments/environments_folder_view.html.haml b/spec/javascripts/fixtures/environments/environments_folder_view.html.haml
new file mode 100644
index 00000000000..aceec139730
--- /dev/null
+++ b/spec/javascripts/fixtures/environments/environments_folder_view.html.haml
@@ -0,0 +1,7 @@
+%div
+ #environments-folder-list-view{ data: { "can-create-deployment" => "true",
+ "can-read-environment" => "true",
+ "css-class" => "",
+ "commit-icon-svg" => custom_icon("icon_commit"),
+ "terminal-icon-svg" => custom_icon("icon_terminal"),
+ "play-icon-svg" => custom_icon("icon_play") } }
diff --git a/spec/javascripts/fixtures/header.html.haml b/spec/javascripts/fixtures/header.html.haml
deleted file mode 100644
index 4db2ef604de..00000000000
--- a/spec/javascripts/fixtures/header.html.haml
+++ /dev/null
@@ -1,35 +0,0 @@
-%header.navbar.navbar-fixed-top.navbar-gitlab.nav_header_class
- .container-fluid
- .header-content
- %button.side-nav-toggle
- %span.sr-only
- Toggle navigation
- %i.fa.fa-bars
- %button.navbar-toggle
- %span.sr-only
- Toggle navigation
- %i.fa.fa-ellipsis-v
- .navbar-collapse.collapse
- %ui.nav.navbar-nav
- %li.hidden-sm.hidden-xs
- %li.visible-sm.visible-xs
- %li
- %a
- %i.fa.fa-bell.fa-fw
- %span.badge.todos-pending-count
- %li
- %a
- %i.fa.fa-plus.fa-fw
- %li.header-user.dropdown
- %a
- %img
- %span.caret
- .dropdown-menu-nav
- .dropdown-menu-align-right
- %ul
- %li
- %a.profile-link
- %li
- %a
- %li.divider
- %li.sign-out-link
diff --git a/spec/javascripts/fixtures/issues.rb b/spec/javascripts/fixtures/issues.rb
index 06f708f9e15..88e3f860809 100644
--- a/spec/javascripts/fixtures/issues.rb
+++ b/spec/javascripts/fixtures/issues.rb
@@ -41,7 +41,7 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller
def render_issue(fixture_file_name, issue)
get :show,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: issue.to_param
expect(response).to be_success
diff --git a/spec/javascripts/fixtures/merge_request_tabs.html.haml b/spec/javascripts/fixtures/merge_request_tabs.html.haml
deleted file mode 100644
index 68678c3d7e3..00000000000
--- a/spec/javascripts/fixtures/merge_request_tabs.html.haml
+++ /dev/null
@@ -1,22 +0,0 @@
-%ul.nav.nav-tabs.merge-request-tabs
- %li.notes-tab
- %a{href: '/foo/bar/merge_requests/1', data: {target: 'div#notes', action: 'notes', toggle: 'tab'}}
- Discussion
- %li.commits-tab
- %a{href: '/foo/bar/merge_requests/1/commits', data: {target: 'div#commits', action: 'commits', toggle: 'tab'}}
- Commits
- %li.diffs-tab
- %a{href: '/foo/bar/merge_requests/1/diffs', data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'}}
- Diffs
-
-.tab-content
- #notes.notes.tab-pane
- Notes Content
- #commits.commits.tab-pane
- Commits Content
- #diffs.diffs.tab-pane
- Diffs Content
-
-.mr-loading-status
- .loading
- Loading Animation
diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb
new file mode 100644
index 00000000000..ee893b76c84
--- /dev/null
+++ b/spec/javascripts/fixtures/merge_requests.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project, namespace: namespace, path: 'merge-requests-project') }
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('merge_requests/')
+ end
+
+ before(:each) do
+ sign_in(admin)
+ end
+
+ it 'merge_requests/merge_request_with_task_list.html.raw' do |example|
+ merge_request = create(:merge_request, :with_diffs, source_project: project, target_project: project, description: '- [ ] Task List Item')
+ render_merge_request(example.description, merge_request)
+ end
+
+ private
+
+ def render_merge_request(fixture_file_name, merge_request)
+ get :show,
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: merge_request.to_param
+
+ expect(response).to be_success
+ store_frontend_fixture(response, fixture_file_name)
+ end
+end
diff --git a/spec/javascripts/fixtures/new_branch.html.haml b/spec/javascripts/fixtures/new_branch.html.haml
deleted file mode 100644
index f06629e5ecc..00000000000
--- a/spec/javascripts/fixtures/new_branch.html.haml
+++ /dev/null
@@ -1,4 +0,0 @@
-%form.js-create-branch-form
- %input.js-branch-name
- .js-branch-name-error
- %input{id: "ref"}
diff --git a/spec/javascripts/fixtures/project_title.html.haml b/spec/javascripts/fixtures/project_title.html.haml
deleted file mode 100644
index 9d1f7877116..00000000000
--- a/spec/javascripts/fixtures/project_title.html.haml
+++ /dev/null
@@ -1,20 +0,0 @@
-.header-content
- %h1.title
- %a
- GitLab Org
- %a.project-item-select-holder{href: "/gitlab-org/gitlab-test"}
- GitLab Test
- %i.fa.chevron-down.dropdown-toggle-caret.js-projects-dropdown-toggle{ "data-toggle" => "dropdown", "data-target" => ".header-content", "data-order-by" => "last_activity_at" }
- .js-dropdown-menu-projects
- .dropdown-menu.dropdown-select.dropdown-menu-projects
- .dropdown-title
- %span Go to a project
- %button.dropdown-title-button.dropdown-menu-close{"aria-label" => "Close", type: "button"}
- %i.fa.fa-times.dropdown-menu-close-icon
- .dropdown-input
- %input.dropdown-input-field{id: "", placeholder: "Search your projects", type: "search", value: ""}
- %i.fa.fa-search.dropdown-input-search
- %i.fa.fa-times.dropdown-input-clear.js-dropdown-input-clear{role: "button"}
- .dropdown-content
- .dropdown-loading
- %i.fa.fa-spinner.fa-spin
diff --git a/spec/javascripts/fixtures/projects.rb b/spec/javascripts/fixtures/projects.rb
index 56513219e1e..6c33b240e5c 100644
--- a/spec/javascripts/fixtures/projects.rb
+++ b/spec/javascripts/fixtures/projects.rb
@@ -20,7 +20,7 @@ describe ProjectsController, '(JavaScript fixtures)', type: :controller do
it 'projects/dashboard.html.raw' do |example|
get :show,
namespace_id: project.namespace.to_param,
- id: project.to_param
+ id: project
expect(response).to be_success
store_frontend_fixture(response, example.description)
diff --git a/spec/javascripts/fixtures/todos.json b/spec/javascripts/fixtures/todos.json
deleted file mode 100644
index 62c2387d515..00000000000
--- a/spec/javascripts/fixtures/todos.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "count": 1,
- "delete_path": "/dashboard/todos/1"
-} \ No newline at end of file
diff --git a/spec/javascripts/fixtures/todos.rb b/spec/javascripts/fixtures/todos.rb
new file mode 100644
index 00000000000..a81ef8c5492
--- /dev/null
+++ b/spec/javascripts/fixtures/todos.rb
@@ -0,0 +1,52 @@
+require 'spec_helper'
+
+describe 'Todos (JavaScript fixtures)' do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project_empty_repo, namespace: namespace, path: 'todos-project') }
+ let(:issue_1) { create(:issue, title: 'issue_1', project: project) }
+ let!(:todo_1) { create(:todo, user: admin, project: project, target: issue_1, created_at: 5.hours.ago) }
+ let(:issue_2) { create(:issue, title: 'issue_2', project: project) }
+ let!(:todo_2) { create(:todo, :done, user: admin, project: project, target: issue_2, created_at: 50.hours.ago) }
+
+ before(:all) do
+ clean_frontend_fixtures('todos/')
+ end
+
+ describe Dashboard::TodosController, '(JavaScript fixtures)', type: :controller do
+ render_views
+
+ before(:each) do
+ sign_in(admin)
+ end
+
+ it 'todos/todos.html.raw' do |example|
+ get :index
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+ end
+
+ describe Projects::TodosController, '(JavaScript fixtures)', type: :controller do
+ render_views
+
+ before(:each) do
+ sign_in(admin)
+ end
+
+ it 'todos/todos.json' do |example|
+ post :create,
+ namespace_id: namespace,
+ project_id: project,
+ issuable_type: 'issue',
+ issuable_id: issue_2.id,
+ format: 'json'
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+ end
+end
diff --git a/spec/javascripts/gfm_auto_complete_spec.js.es6 b/spec/javascripts/gfm_auto_complete_spec.js.es6
index c61c32f8a13..5dfa4008fbd 100644
--- a/spec/javascripts/gfm_auto_complete_spec.js.es6
+++ b/spec/javascripts/gfm_auto_complete_spec.js.es6
@@ -1,3 +1,5 @@
+/* eslint no-param-reassign: "off" */
+
require('~/gfm_auto_complete');
require('vendor/jquery.caret');
require('vendor/jquery.atwho');
@@ -63,6 +65,61 @@ describe('GfmAutoComplete', function () {
});
});
+ describe('DefaultOptions.matcher', function () {
+ const defaultMatcher = (context, flag, subtext) => (
+ GfmAutoComplete.DefaultOptions.matcher.call(context, flag, subtext)
+ );
+
+ const flagsUseDefaultMatcher = ['@', '#', '!', '~', '%'];
+ const otherFlags = ['/', ':'];
+ const flags = flagsUseDefaultMatcher.concat(otherFlags);
+
+ const flagsHash = flags.reduce((hash, el) => { hash[el] = null; return hash; }, {});
+ const atwhoInstance = { setting: {}, app: { controllers: flagsHash } };
+
+ const minLen = 1;
+ const maxLen = 20;
+ const argumentSize = [minLen, maxLen / 2, maxLen];
+
+ const allowedSymbols = ['', 'a', 'n', 'z', 'A', 'Z', 'N', '0', '5', '9', 'А', 'а', 'Я', 'я', '.', '\'', '+', '-', '_'];
+ const jointAllowedSymbols = allowedSymbols.join('');
+
+ describe('should match regular symbols', () => {
+ flagsUseDefaultMatcher.forEach((flag) => {
+ allowedSymbols.forEach((symbol) => {
+ argumentSize.forEach((size) => {
+ const query = new Array(size + 1).join(symbol);
+ const subtext = flag + query;
+
+ it(`matches argument "${flag}" with query "${subtext}"`, () => {
+ expect(defaultMatcher(atwhoInstance, flag, subtext)).toBe(query);
+ });
+ });
+ });
+
+ it(`matches combination of allowed symbols for flag "${flag}"`, () => {
+ const subtext = flag + jointAllowedSymbols;
+
+ expect(defaultMatcher(atwhoInstance, flag, subtext)).toBe(jointAllowedSymbols);
+ });
+ });
+ });
+
+ describe('should not match special sequences', () => {
+ const ShouldNotBeFollowedBy = flags.concat(['\x00', '\x10', '\x3f', '\n', ' ']);
+
+ flagsUseDefaultMatcher.forEach((atSign) => {
+ ShouldNotBeFollowedBy.forEach((followedSymbol) => {
+ const seq = atSign + followedSymbol;
+
+ it(`should not match "${seq}"`, () => {
+ expect(defaultMatcher(atwhoInstance, atSign, seq)).toBe(null);
+ });
+ });
+ });
+ });
+ });
+
describe('isLoading', function () {
it('should be true with loading data object item', function () {
expect(GfmAutoComplete.isLoading({ name: 'loading' })).toBe(true);
diff --git a/spec/javascripts/gl_dropdown_spec.js.es6 b/spec/javascripts/gl_dropdown_spec.js.es6
index 317f38c5888..c207fb00a47 100644
--- a/spec/javascripts/gl_dropdown_spec.js.es6
+++ b/spec/javascripts/gl_dropdown_spec.js.es6
@@ -139,6 +139,14 @@ require('~/lib/utils/url_utility');
this.dropdownButtonElement.click();
});
+ it('should show loading indicator while search results are being fetched by backend', () => {
+ const dropdownMenu = document.querySelector('.dropdown-menu');
+
+ expect(dropdownMenu.className.indexOf('is-loading') !== -1).toEqual(true);
+ remoteCallback();
+ expect(dropdownMenu.className.indexOf('is-loading') !== -1).toEqual(false);
+ });
+
it('should not focus search input while remote task is not complete', () => {
expect($(document.activeElement)).not.toEqual($(SEARCH_INPUT_SELECTOR));
remoteCallback();
diff --git a/spec/javascripts/header_spec.js b/spec/javascripts/header_spec.js
index cecebb0b038..46a27b8c98f 100644
--- a/spec/javascripts/header_spec.js
+++ b/spec/javascripts/header_spec.js
@@ -6,7 +6,7 @@ require('~/lib/utils/text_utility');
(function() {
describe('Header', function() {
var todosPendingCount = '.todos-pending-count';
- var fixtureTemplate = 'static/header.html.raw';
+ var fixtureTemplate = 'issues/open-issue.html.raw';
function isTodosCountHidden() {
return $(todosPendingCount).hasClass('hidden');
@@ -45,9 +45,9 @@ require('~/lib/utils/text_utility');
expect(isTodosCountHidden()).toEqual(false);
});
- it('should add delimiter to todos-pending-count', function() {
- expect($(todosPendingCount).text()).toEqual('1,000');
+ it('should show 99+ for todos-pending-count', function() {
+ expect($(todosPendingCount).text()).toEqual('99+');
});
});
});
-}).call(this);
+}).call(window);
diff --git a/spec/javascripts/helpers/class_spec_helper.js.es6 b/spec/javascripts/helpers/class_spec_helper.js.es6
index d3c37d39431..61db27a8fcc 100644
--- a/spec/javascripts/helpers/class_spec_helper.js.es6
+++ b/spec/javascripts/helpers/class_spec_helper.js.es6
@@ -7,3 +7,5 @@ class ClassSpecHelper {
}
window.ClassSpecHelper = ClassSpecHelper;
+
+module.exports = ClassSpecHelper;
diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js
index 5b0b7aa7903..e7530f61385 100644
--- a/spec/javascripts/issue_spec.js
+++ b/spec/javascripts/issue_spec.js
@@ -105,6 +105,7 @@ require('~/issue');
expectIssueState(false);
expect($btnClose).toHaveProp('disabled', false);
+ expect($('.issue_counter')).toHaveText(0);
});
it('fails to close an issue with success:false', function() {
@@ -121,6 +122,7 @@ require('~/issue');
expectIssueState(true);
expect($btnClose).toHaveProp('disabled', false);
expectErrorMessage();
+ expect($('.issue_counter')).toHaveText(1);
});
it('fails to closes an issue with HTTP error', function() {
@@ -135,6 +137,7 @@ require('~/issue');
expectIssueState(true);
expect($btnClose).toHaveProp('disabled', true);
expectErrorMessage();
+ expect($('.issue_counter')).toHaveText(1);
});
});
@@ -159,6 +162,7 @@ require('~/issue');
expectIssueState(true);
expect($btnReopen).toHaveProp('disabled', false);
+ expect($('.issue_counter')).toHaveText(1);
});
});
-}).call(this);
+}).call(window);
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js.es6 b/spec/javascripts/lib/utils/common_utils_spec.js.es6
index 006ede21093..f4d3e77e515 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js.es6
+++ b/spec/javascripts/lib/utils/common_utils_spec.js.es6
@@ -108,6 +108,30 @@ require('~/lib/utils/common_utils');
});
});
+ describe('gl.utils.parseIntPagination', () => {
+ it('should parse to integers all string values and return pagination object', () => {
+ const pagination = {
+ 'X-PER-PAGE': 10,
+ 'X-PAGE': 2,
+ 'X-TOTAL': 30,
+ 'X-TOTAL-PAGES': 3,
+ 'X-NEXT-PAGE': 3,
+ 'X-PREV-PAGE': 1,
+ };
+
+ const expectedPagination = {
+ perPage: 10,
+ page: 2,
+ total: 30,
+ totalPages: 3,
+ nextPage: 3,
+ previousPage: 1,
+ };
+
+ expect(gl.utils.parseIntPagination(pagination)).toEqual(expectedPagination);
+ });
+ });
+
describe('gl.utils.isMetaClick', () => {
it('should identify meta click on Windows/Linux', () => {
const e = {
diff --git a/spec/javascripts/lib/utils/text_utility_spec.js.es6 b/spec/javascripts/lib/utils/text_utility_spec.js.es6
index 86ade66ec29..06b69b8ac17 100644
--- a/spec/javascripts/lib/utils/text_utility_spec.js.es6
+++ b/spec/javascripts/lib/utils/text_utility_spec.js.es6
@@ -35,5 +35,16 @@ require('~/lib/utils/text_utility');
expect(gl.text.pluralize('test', 1)).toBe('test');
});
});
+
+ describe('gl.text.highCountTrim', () => {
+ it('returns 99+ for count >= 100', () => {
+ expect(gl.text.highCountTrim(105)).toBe('99+');
+ expect(gl.text.highCountTrim(100)).toBe('99+');
+ });
+
+ it('returns exact number for count < 100', () => {
+ expect(gl.text.highCountTrim(45)).toBe(45);
+ });
+ });
});
})();
diff --git a/spec/javascripts/line_highlighter_spec.js b/spec/javascripts/line_highlighter_spec.js
index 8b196f7720f..a0b2ebc221b 100644
--- a/spec/javascripts/line_highlighter_spec.js
+++ b/spec/javascripts/line_highlighter_spec.js
@@ -227,4 +227,4 @@ require('~/line_highlighter');
});
});
});
-}).call(this);
+}).call(window);
diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js
index 25cfa9e9479..fd97dced870 100644
--- a/spec/javascripts/merge_request_spec.js
+++ b/spec/javascripts/merge_request_spec.js
@@ -6,9 +6,9 @@ require('~/merge_request');
(function() {
describe('MergeRequest', function() {
return describe('task lists', function() {
- preloadFixtures('static/merge_requests_show.html.raw');
+ preloadFixtures('merge_requests/merge_request_with_task_list.html.raw');
beforeEach(function() {
- loadFixtures('static/merge_requests_show.html.raw');
+ loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
return this.merge = new MergeRequest();
});
it('modifies the Markdown field', function() {
@@ -19,11 +19,11 @@ require('~/merge_request');
return it('submits an ajax request on tasklist:changed', function() {
spyOn(jQuery, 'ajax').and.callFake(function(req) {
expect(req.type).toBe('PATCH');
- expect(req.url).toBe('/foo');
+ expect(req.url).toBe(`${gl.TEST_HOST}/frontend-fixtures/merge-requests-project/merge_requests/1.json`);
return expect(req.data.merge_request.description).not.toBe(null);
});
return $('.js-task-list-field').trigger('tasklist:changed');
});
});
});
-}).call(this);
+}).call(window);
diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js
index 92a0f1c05f7..7506e6ab49e 100644
--- a/spec/javascripts/merge_request_tabs_spec.js
+++ b/spec/javascripts/merge_request_tabs_spec.js
@@ -25,7 +25,7 @@ require('vendor/jquery.scrollTo');
};
$.extend(stubLocation, defaults, stubs || {});
};
- preloadFixtures('static/merge_request_tabs.html.raw');
+ preloadFixtures('merge_requests/merge_request_with_task_list.html.raw');
beforeEach(function () {
this.class = new gl.MergeRequestTabs({ stubLocation: stubLocation });
@@ -41,7 +41,7 @@ require('vendor/jquery.scrollTo');
describe('#activateTab', function () {
beforeEach(function () {
spyOn($, 'ajax').and.callFake(function () {});
- loadFixtures('static/merge_request_tabs.html.raw');
+ loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
this.subject = this.class.activateTab;
});
it('shows the first tab when action is show', function () {
@@ -62,19 +62,47 @@ require('vendor/jquery.scrollTo');
});
});
describe('#opensInNewTab', function () {
- var commitsLink;
var tabUrl;
+ var windowTarget = '_blank';
beforeEach(function () {
- commitsLink = '.commits-tab li a';
- tabUrl = $(commitsLink).attr('href');
+ loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
+
+ tabUrl = $('.commits-tab a').attr('href');
spyOn($.fn, 'attr').and.returnValue(tabUrl);
});
+
+ describe('meta click', () => {
+ beforeEach(function () {
+ spyOn(gl.utils, 'isMetaClick').and.returnValue(true);
+ });
+
+ it('opens page when commits link is clicked', function () {
+ spyOn(window, 'open').and.callFake(function (url, name) {
+ expect(url).toEqual(tabUrl);
+ expect(name).toEqual(windowTarget);
+ });
+
+ this.class.bindEvents();
+ document.querySelector('.merge-request-tabs .commits-tab a').click();
+ });
+
+ it('opens page when commits badge is clicked', function () {
+ spyOn(window, 'open').and.callFake(function (url, name) {
+ expect(url).toEqual(tabUrl);
+ expect(name).toEqual(windowTarget);
+ });
+
+ this.class.bindEvents();
+ document.querySelector('.merge-request-tabs .commits-tab a .badge').click();
+ });
+ });
+
it('opens page tab in a new browser tab with Ctrl+Click - Windows/Linux', function () {
spyOn(window, 'open').and.callFake(function (url, name) {
expect(url).toEqual(tabUrl);
- expect(name).toEqual('_blank');
+ expect(name).toEqual(windowTarget);
});
this.class.clickTab({
@@ -87,7 +115,7 @@ require('vendor/jquery.scrollTo');
it('opens page tab in a new browser tab with Cmd+Click - Mac', function () {
spyOn(window, 'open').and.callFake(function (url, name) {
expect(url).toEqual(tabUrl);
- expect(name).toEqual('_blank');
+ expect(name).toEqual(windowTarget);
});
this.class.clickTab({
@@ -100,7 +128,7 @@ require('vendor/jquery.scrollTo');
it('opens page tab in a new browser tab with Middle-click - Mac/PC', function () {
spyOn(window, 'open').and.callFake(function (url, name) {
expect(url).toEqual(tabUrl);
- expect(name).toEqual('_blank');
+ expect(name).toEqual(windowTarget);
});
this.class.clickTab({
@@ -181,4 +209,4 @@ require('vendor/jquery.scrollTo');
});
});
});
-}).call(this);
+}).call(window);
diff --git a/spec/javascripts/merge_request_widget_spec.js b/spec/javascripts/merge_request_widget_spec.js
index 8cefdd2409d..d5193b41c33 100644
--- a/spec/javascripts/merge_request_widget_spec.js
+++ b/spec/javascripts/merge_request_widget_spec.js
@@ -189,4 +189,4 @@ require('~/lib/utils/datetime_utility');
});
});
});
-}).call(this);
+}).call(window);
diff --git a/spec/javascripts/new_branch_spec.js b/spec/javascripts/new_branch_spec.js
index 9b657868523..f132537b943 100644
--- a/spec/javascripts/new_branch_spec.js
+++ b/spec/javascripts/new_branch_spec.js
@@ -8,7 +8,7 @@ require('~/new_branch_form');
describe('Branch', function() {
return describe('create a new branch', function() {
var expectToHaveError, fillNameWith;
- preloadFixtures('static/new_branch.html.raw');
+ preloadFixtures('branches/new_branch.html.raw');
fillNameWith = function(value) {
return $('.js-branch-name').val(value).trigger('blur');
};
@@ -16,7 +16,7 @@ require('~/new_branch_form');
return expect($('.js-branch-name-error span').text()).toEqual(error);
};
beforeEach(function() {
- loadFixtures('static/new_branch.html.raw');
+ loadFixtures('branches/new_branch.html.raw');
$('form').on('submit', function(e) {
return e.preventDefault();
});
@@ -166,4 +166,4 @@ require('~/new_branch_form');
});
});
});
-}).call(this);
+}).call(window);
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
index af495787c54..d81a5bbb6a5 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/javascripts/notes_spec.js
@@ -35,15 +35,13 @@ require('~/lib/utils/text_utility');
expect($('.js-task-list-field').val()).toBe('- [x] Task List Item');
});
- it('submits the form on tasklist:changed', function() {
- var submitted = false;
- $('form').on('submit', function(e) {
- submitted = true;
- e.preventDefault();
+ it('submits an ajax request on tasklist:changed', function() {
+ spyOn(jQuery, 'ajax').and.callFake(function(req) {
+ expect(req.type).toBe('PATCH');
+ expect(req.url).toBe('http://test.host/frontend-fixtures/issues-project/notes/1');
+ return expect(req.data.note).not.toBe(null);
});
-
$('.js-task-list-field').trigger('tasklist:changed');
- expect(submitted).toBe(true);
});
});
@@ -75,4 +73,4 @@ require('~/lib/utils/text_utility');
});
});
});
-}).call(this);
+}).call(window);
diff --git a/spec/javascripts/project_dashboard_spec.js.es6 b/spec/javascripts/project_dashboard_spec.js.es6
deleted file mode 100644
index 24833b4eb57..00000000000
--- a/spec/javascripts/project_dashboard_spec.js.es6
+++ /dev/null
@@ -1,86 +0,0 @@
-require('~/sidebar');
-
-(() => {
- describe('Project dashboard page', () => {
- let $pageWithSidebar = null;
- let $sidebarToggle = null;
- let sidebar = null;
- const fixtureTemplate = 'projects/dashboard.html.raw';
-
- const assertSidebarStateExpanded = (shouldBeExpanded) => {
- expect(sidebar.isExpanded).toBe(shouldBeExpanded);
- expect($pageWithSidebar.hasClass('page-sidebar-expanded')).toBe(shouldBeExpanded);
- };
-
- preloadFixtures(fixtureTemplate);
- beforeEach(() => {
- loadFixtures(fixtureTemplate);
-
- $pageWithSidebar = $('.page-with-sidebar');
- $sidebarToggle = $('.toggle-nav-collapse');
-
- // otherwise instantiating the Sidebar for the second time
- // won't do anything, as the Sidebar is a singleton class
- gl.Sidebar.singleton = null;
- sidebar = new gl.Sidebar();
- });
-
- it('can show the sidebar when the toggler is clicked', () => {
- assertSidebarStateExpanded(false);
- $sidebarToggle.click();
- assertSidebarStateExpanded(true);
- });
-
- it('should dismiss the sidebar when clone button clicked', () => {
- $sidebarToggle.click();
- assertSidebarStateExpanded(true);
-
- const cloneButton = $('.project-clone-holder a.clone-dropdown-btn');
- cloneButton.click();
- assertSidebarStateExpanded(false);
- });
-
- it('should dismiss the sidebar when download button clicked', () => {
- $sidebarToggle.click();
- assertSidebarStateExpanded(true);
-
- const downloadButton = $('.project-action-button .btn:has(i.fa-download)');
- downloadButton.click();
- assertSidebarStateExpanded(false);
- });
-
- it('should dismiss the sidebar when add button clicked', () => {
- $sidebarToggle.click();
- assertSidebarStateExpanded(true);
-
- const addButton = $('.project-action-button .btn:has(i.fa-plus)');
- addButton.click();
- assertSidebarStateExpanded(false);
- });
-
- it('should dismiss the sidebar when notification button clicked', () => {
- $sidebarToggle.click();
- assertSidebarStateExpanded(true);
-
- const notifButton = $('.js-notification-toggle-btns .notifications-btn');
- notifButton.click();
- assertSidebarStateExpanded(false);
- });
-
- it('should dismiss the sidebar when clicking on the body', () => {
- $sidebarToggle.click();
- assertSidebarStateExpanded(true);
-
- $('body').click();
- assertSidebarStateExpanded(false);
- });
-
- it('should dismiss the sidebar when clicking on the project description header', () => {
- $sidebarToggle.click();
- assertSidebarStateExpanded(true);
-
- $('.project-home-panel').click();
- assertSidebarStateExpanded(false);
- });
- });
-})();
diff --git a/spec/javascripts/project_title_spec.js b/spec/javascripts/project_title_spec.js
index bfe3d2df79d..69d9587771f 100644
--- a/spec/javascripts/project_title_spec.js
+++ b/spec/javascripts/project_title_spec.js
@@ -10,11 +10,11 @@ require('~/project');
(function() {
describe('Project Title', function() {
- preloadFixtures('static/project_title.html.raw');
+ preloadFixtures('issues/open-issue.html.raw');
loadJSONFixtures('projects.json');
beforeEach(function() {
- loadFixtures('static/project_title.html.raw');
+ loadFixtures('issues/open-issue.html.raw');
window.gon = {};
window.gon.api_version = 'v3';
@@ -38,15 +38,12 @@ require('~/project');
return spyOn(jQuery, 'ajax').and.callFake(fakeAjaxResponse.bind(_this));
};
})(this));
- it('to show on toggle click', (function(_this) {
- return function() {
- $('.js-projects-dropdown-toggle').click();
- return expect($('.header-content').hasClass('open')).toBe(true);
- };
- })(this));
- return it('hide dropdown', function() {
- $(".dropdown-menu-close-icon").click();
- return expect($('.header-content').hasClass('open')).toBe(false);
+ it('toggles dropdown', function() {
+ var menu = $('.js-dropdown-menu-projects');
+ $('.js-projects-dropdown-toggle').click();
+ expect(menu).toHaveClass('open');
+ menu.find('.dropdown-menu-close-icon').click();
+ expect(menu).not.toHaveClass('open');
});
});
@@ -54,4 +51,4 @@ require('~/project');
window.gon = {};
});
});
-}).call(this);
+}).call(window);
diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js
index f7636865aa1..4ac7e911740 100644
--- a/spec/javascripts/right_sidebar_spec.js
+++ b/spec/javascripts/right_sidebar_spec.js
@@ -34,7 +34,7 @@ require('~/extensions/jquery.js');
describe('RightSidebar', function() {
var fixtureName = 'issues/open-issue.html.raw';
preloadFixtures(fixtureName);
- loadJSONFixtures('todos.json');
+ loadJSONFixtures('todos/todos.json');
beforeEach(function() {
loadFixtures(fixtureName);
@@ -64,7 +64,7 @@ require('~/extensions/jquery.js');
});
it('should broadcast todo:toggle event when add todo clicked', function() {
- var todos = getJSONFixture('todos.json');
+ var todos = getJSONFixture('todos/todos.json');
spyOn(jQuery, 'ajax').and.callFake(function() {
var d = $.Deferred();
var response = todos;
@@ -79,4 +79,4 @@ require('~/extensions/jquery.js');
expect(todoToggleSpy.calls.count()).toEqual(1);
});
});
-}).call(this);
+}).call(window);
diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js
index 9572b52ec1e..aaf058bd755 100644
--- a/spec/javascripts/search_autocomplete_spec.js
+++ b/spec/javascripts/search_autocomplete_spec.js
@@ -89,8 +89,8 @@ require('vendor/fuzzaldrin-plus');
var a1, a2, a3, a4, issuesAssignedToMeLink, issuesIHaveCreatedLink, mrsAssignedToMeLink, mrsIHaveCreatedLink;
issuesAssignedToMeLink = issuesPath + "/?assignee_username=" + userName;
issuesIHaveCreatedLink = issuesPath + "/?author_username=" + userName;
- mrsAssignedToMeLink = mrsPath + "/?assignee_id=" + userId;
- mrsIHaveCreatedLink = mrsPath + "/?author_id=" + userId;
+ mrsAssignedToMeLink = mrsPath + "/?assignee_username=" + userName;
+ mrsIHaveCreatedLink = mrsPath + "/?author_username=" + userName;
a1 = "a[href='" + issuesAssignedToMeLink + "']";
a2 = "a[href='" + issuesIHaveCreatedLink + "']";
a3 = "a[href='" + mrsAssignedToMeLink + "']";
@@ -175,4 +175,4 @@ require('vendor/fuzzaldrin-plus');
expect(enterKeyEvent.isDefaultPrevented()).toBe(true);
});
});
-}).call(this);
+}).call(window);
diff --git a/spec/javascripts/shortcuts_issuable_spec.js b/spec/javascripts/shortcuts_issuable_spec.js
index 602ac01aec3..ffff643e371 100644
--- a/spec/javascripts/shortcuts_issuable_spec.js
+++ b/spec/javascripts/shortcuts_issuable_spec.js
@@ -79,4 +79,4 @@ require('~/shortcuts_issuable');
});
});
});
-}).call(this);
+}).call(window);
diff --git a/spec/javascripts/syntax_highlight_spec.js b/spec/javascripts/syntax_highlight_spec.js
index c0c3837d1f4..cea223bd243 100644
--- a/spec/javascripts/syntax_highlight_spec.js
+++ b/spec/javascripts/syntax_highlight_spec.js
@@ -41,4 +41,4 @@ require('~/syntax_highlight');
});
});
});
-}).call(this);
+}).call(window);
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index 7df8d2fd8b4..ca707d872a4 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -7,7 +7,7 @@ jasmine.getJSONFixtures().fixturesPath = 'base/spec/javascripts/fixtures';
// include common libraries
window.$ = window.jQuery = require('jquery');
window._ = require('underscore');
-window.Cookies = require('vendor/js.cookie');
+window.Cookies = require('js-cookie');
window.Vue = require('vue');
window.Vue.use(require('vue-resource'));
require('jquery-ujs');
@@ -42,3 +42,38 @@ testsContext.keys().forEach(function (path) {
});
}
});
+
+// workaround: include all source files to find files with 0% coverage
+// see also https://github.com/deepsweet/istanbul-instrumenter-loader/issues/15
+describe('Uncovered files', function () {
+ // the following files throw errors because of undefined variables
+ const troubleMakers = [
+ './blob_edit/blob_edit_bundle.js',
+ './cycle_analytics/components/stage_plan_component.js',
+ './cycle_analytics/components/stage_staging_component.js',
+ './cycle_analytics/components/stage_test_component.js',
+ './diff_notes/components/jump_to_discussion.js',
+ './diff_notes/components/resolve_count.js',
+ './merge_conflicts/components/inline_conflict_lines.js',
+ './merge_conflicts/components/parallel_conflict_lines.js',
+ './network/branch_graph.js',
+ ];
+
+ const sourceFiles = require.context('~', true, /^\.\/(?!application\.js).*\.(js|es6)$/);
+ sourceFiles.keys().forEach(function (path) {
+ // ignore if there is a matching spec file
+ if (testsContext.keys().indexOf(`${path.replace(/\.js(\.es6)?$/, '')}_spec`) > -1) {
+ return;
+ }
+
+ it(`includes '${path}'`, function () {
+ try {
+ sourceFiles(path);
+ } catch (err) {
+ if (troubleMakers.indexOf(path) === -1) {
+ expect(err).toBeNull();
+ }
+ }
+ });
+ });
+});
diff --git a/spec/javascripts/todos_spec.js b/spec/javascripts/todos_spec.js
new file mode 100644
index 00000000000..66e4fbd6304
--- /dev/null
+++ b/spec/javascripts/todos_spec.js
@@ -0,0 +1,63 @@
+require('~/todos');
+require('~/lib/utils/common_utils');
+
+describe('Todos', () => {
+ preloadFixtures('todos/todos.html.raw');
+ let todoItem;
+
+ beforeEach(() => {
+ loadFixtures('todos/todos.html.raw');
+ todoItem = document.querySelector('.todos-list .todo');
+
+ return new gl.Todos();
+ });
+
+ describe('goToTodoUrl', () => {
+ it('opens the todo url', (done) => {
+ const todoLink = todoItem.dataset.url;
+
+ spyOn(gl.utils, 'visitUrl').and.callFake((url) => {
+ expect(url).toEqual(todoLink);
+ done();
+ });
+
+ todoItem.click();
+ });
+
+ describe('meta click', () => {
+ let visitUrlSpy;
+
+ beforeEach(() => {
+ spyOn(gl.utils, 'isMetaClick').and.returnValue(true);
+ visitUrlSpy = spyOn(gl.utils, 'visitUrl').and.callFake(() => {});
+ });
+
+ it('opens the todo url in another tab', (done) => {
+ const todoLink = todoItem.dataset.url;
+
+ spyOn(window, 'open').and.callFake((url, target) => {
+ expect(todoLink).toEqual(url);
+ expect(target).toEqual('_blank');
+ done();
+ });
+
+ todoItem.click();
+ expect(visitUrlSpy).not.toHaveBeenCalled();
+ });
+
+ it('opens the avatar\'s url in another tab when the avatar is clicked', (done) => {
+ const avatarImage = todoItem.querySelector('img');
+ const avatarUrl = avatarImage.parentElement.getAttribute('href');
+
+ spyOn(window, 'open').and.callFake((url, target) => {
+ expect(avatarUrl).toEqual(url);
+ expect(target).toEqual('_blank');
+ done();
+ });
+
+ avatarImage.click();
+ expect(visitUrlSpy).not.toHaveBeenCalled();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/u2f/authenticate_spec.js b/spec/javascripts/u2f/authenticate_spec.js
index cba1af4daa4..af2d02b6b29 100644
--- a/spec/javascripts/u2f/authenticate_spec.js
+++ b/spec/javascripts/u2f/authenticate_spec.js
@@ -69,4 +69,4 @@ require('./mock_u2f_device');
});
});
});
-}).call(this);
+}).call(window);
diff --git a/spec/javascripts/u2f/mock_u2f_device.js b/spec/javascripts/u2f/mock_u2f_device.js
index 287bfb4138b..6677fe9c1ee 100644
--- a/spec/javascripts/u2f/mock_u2f_device.js
+++ b/spec/javascripts/u2f/mock_u2f_device.js
@@ -30,4 +30,4 @@
return MockU2FDevice;
})();
-}).call(this);
+}).call(window);
diff --git a/spec/javascripts/u2f/register_spec.js b/spec/javascripts/u2f/register_spec.js
index 10578c2c4b5..0f390c8b980 100644
--- a/spec/javascripts/u2f/register_spec.js
+++ b/spec/javascripts/u2f/register_spec.js
@@ -74,4 +74,4 @@ require('./mock_u2f_device');
});
});
});
-}).call(this);
+}).call(window);
diff --git a/spec/javascripts/version_check_image_spec.js.es6 b/spec/javascripts/version_check_image_spec.js.es6
new file mode 100644
index 00000000000..464c1fce210
--- /dev/null
+++ b/spec/javascripts/version_check_image_spec.js.es6
@@ -0,0 +1,33 @@
+const ClassSpecHelper = require('./helpers/class_spec_helper');
+const VersionCheckImage = require('~/version_check_image');
+require('jquery');
+
+describe('VersionCheckImage', function () {
+ describe('.bindErrorEvent', function () {
+ ClassSpecHelper.itShouldBeAStaticMethod(VersionCheckImage, 'bindErrorEvent');
+
+ beforeEach(function () {
+ this.imageElement = $('<div></div>');
+ });
+
+ it('registers an error event', function () {
+ spyOn($.prototype, 'on');
+ spyOn($.prototype, 'off').and.callFake(function () { return this; });
+
+ VersionCheckImage.bindErrorEvent(this.imageElement);
+
+ expect($.prototype.off).toHaveBeenCalledWith('error');
+ expect($.prototype.on).toHaveBeenCalledWith('error', jasmine.any(Function));
+ });
+
+ it('hides the imageElement on error', function () {
+ spyOn($.prototype, 'hide');
+
+ VersionCheckImage.bindErrorEvent(this.imageElement);
+
+ this.imageElement.trigger('error');
+
+ expect($.prototype.hide).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/table_pagination_spec.js.es6 b/spec/javascripts/vue_shared/components/table_pagination_spec.js.es6
index e84f0dcfe67..dd495cb43bc 100644
--- a/spec/javascripts/vue_shared/components/table_pagination_spec.js.es6
+++ b/spec/javascripts/vue_shared/components/table_pagination_spec.js.es6
@@ -34,7 +34,7 @@ describe('Pagination component', () => {
component.changePage({ target: { innerText: '1' } });
expect(changeChanges.one).toEqual(1);
- expect(changeChanges.two).toEqual('all');
+ expect(changeChanges.two).toEqual(null);
});
it('should go to the previous page', () => {
@@ -55,7 +55,7 @@ describe('Pagination component', () => {
component.changePage({ target: { innerText: 'Prev' } });
expect(changeChanges.one).toEqual(1);
- expect(changeChanges.two).toEqual('all');
+ expect(changeChanges.two).toEqual(null);
});
it('should go to the next page', () => {
@@ -76,7 +76,7 @@ describe('Pagination component', () => {
component.changePage({ target: { innerText: 'Next' } });
expect(changeChanges.one).toEqual(5);
- expect(changeChanges.two).toEqual('all');
+ expect(changeChanges.two).toEqual(null);
});
it('should go to the last page', () => {
@@ -97,7 +97,7 @@ describe('Pagination component', () => {
component.changePage({ target: { innerText: 'Last >>' } });
expect(changeChanges.one).toEqual(10);
- expect(changeChanges.two).toEqual('all');
+ expect(changeChanges.two).toEqual(null);
});
it('should go to the first page', () => {
@@ -118,7 +118,7 @@ describe('Pagination component', () => {
component.changePage({ target: { innerText: '<< First' } });
expect(changeChanges.one).toEqual(1);
- expect(changeChanges.two).toEqual('all');
+ expect(changeChanges.two).toEqual(null);
});
it('should do nothing', () => {
@@ -139,7 +139,7 @@ describe('Pagination component', () => {
component.changePage({ target: { innerText: '...' } });
expect(changeChanges.one).toEqual(1);
- expect(changeChanges.two).toEqual('all');
+ expect(changeChanges.two).toEqual(null);
});
});
diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js
index ce33a6814aa..99515f2e5f2 100644
--- a/spec/javascripts/zen_mode_spec.js
+++ b/spec/javascripts/zen_mode_spec.js
@@ -76,4 +76,4 @@ require('~/zen_mode');
keyCode: 27
}));
};
-}).call(this);
+}).call(window);
diff --git a/spec/lib/additional_email_headers_interceptor_spec.rb b/spec/lib/additional_email_headers_interceptor_spec.rb
new file mode 100644
index 00000000000..580450eef1e
--- /dev/null
+++ b/spec/lib/additional_email_headers_interceptor_spec.rb
@@ -0,0 +1,12 @@
+require 'spec_helper'
+
+describe AdditionalEmailHeadersInterceptor do
+ it 'adds Auto-Submitted header' do
+ mail = ActionMailer::Base.mail(to: 'test@mail.com', from: 'info@mail.com', body: 'hello').deliver
+
+ expect(mail.header['To'].value).to eq('test@mail.com')
+ expect(mail.header['From'].value).to eq('info@mail.com')
+ expect(mail.header['Auto-Submitted'].value).to eq('auto-generated')
+ expect(mail.header['X-Auto-Response-Suppress'].value).to eq('All')
+ end
+end
diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
index 456dbac0698..11607d4fb26 100644
--- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
@@ -311,7 +311,7 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
end
end
- describe '#issues_per_Project' do
+ describe '#issues_per_project' do
context 'using an internal issue tracker' do
it 'returns a Hash containing the issues per project' do
doc = Nokogiri::HTML.fragment('')
@@ -346,4 +346,26 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
end
end
end
+
+ describe '.references_in' do
+ let(:merge_request) { create(:merge_request) }
+
+ it 'yields valid references' do
+ expect do |b|
+ described_class.references_in(issue.to_reference, &b)
+ end.to yield_with_args(issue.to_reference, issue.iid, nil, nil, MatchData)
+ end
+
+ it "doesn't yield invalid references" do
+ expect do |b|
+ described_class.references_in('#0', &b)
+ end.not_to yield_control
+ end
+
+ it "doesn't yield unsupported references" do
+ expect do |b|
+ described_class.references_in(merge_request.to_reference, &b)
+ end.not_to yield_control
+ end
+ end
end
diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb
index 3e1ac9fb2b2..d5d128c1907 100644
--- a/spec/lib/banzai/filter/user_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb
@@ -112,6 +112,19 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do
end
end
+ context 'mentioning a nested group' do
+ it_behaves_like 'a reference containing an element node'
+
+ let(:group) { create(:group, :nested) }
+ let(:reference) { group.to_reference }
+
+ it 'links to the nested group' do
+ doc = reference_filter("Hey #{reference}")
+
+ expect(doc.css('a').first.attr('href')).to eq urls.group_url(group)
+ end
+ end
+
it 'links with adjacent text' do
doc = reference_filter("Mention me (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/)
diff --git a/spec/lib/bitbucket/collection_spec.rb b/spec/lib/bitbucket/collection_spec.rb
index 015a7f80e03..9008cb3e870 100644
--- a/spec/lib/bitbucket/collection_spec.rb
+++ b/spec/lib/bitbucket/collection_spec.rb
@@ -19,6 +19,6 @@ describe Bitbucket::Collection do
it "iterates paginator" do
collection = described_class.new(TestPaginator.new)
- expect(collection.to_a).to match(["result_1_page_1", "result_2_page_1", "result_1_page_2", "result_2_page_2"])
+ expect(collection.to_a).to match(%w(result_1_page_1 result_2_page_1 result_1_page_2 result_2_page_2))
end
end
diff --git a/spec/lib/bitbucket/representation/repo_spec.rb b/spec/lib/bitbucket/representation/repo_spec.rb
index adcd978e1b3..405265cc669 100644
--- a/spec/lib/bitbucket/representation/repo_spec.rb
+++ b/spec/lib/bitbucket/representation/repo_spec.rb
@@ -29,7 +29,7 @@ describe Bitbucket::Representation::Repo do
end
describe '#owner_and_slug' do
- it { expect(described_class.new({ 'full_name' => 'ben/test' }).owner_and_slug).to eq(['ben', 'test']) }
+ it { expect(described_class.new({ 'full_name' => 'ben/test' }).owner_and_slug).to eq(%w(ben test)) }
end
describe '#owner' do
@@ -42,7 +42,7 @@ describe Bitbucket::Representation::Repo do
describe '#clone_url' do
it 'builds url' do
- data = { 'links' => { 'clone' => [ { 'name' => 'https', 'href' => 'https://bibucket.org/test/test.git' }] } }
+ data = { 'links' => { 'clone' => [{ 'name' => 'https', 'href' => 'https://bibucket.org/test/test.git' }] } }
expect(described_class.new(data).clone_url('abc')).to eq('https://x-token-auth:abc@bibucket.org/test/test.git')
end
end
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index 008c15c4de3..7145f0da1d3 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -4,6 +4,16 @@ module Ci
describe GitlabCiYamlProcessor, lib: true do
let(:path) { 'path' }
+ describe 'our current .gitlab-ci.yml' do
+ let(:config) { File.read("#{Rails.root}/.gitlab-ci.yml") }
+
+ it 'is valid' do
+ error_message = described_class.validation_message(config)
+
+ expect(error_message).to be_nil
+ end
+ end
+
describe '#build_attributes' do
describe 'coverage entry' do
subject { described_class.new(config, path).build_attributes(:rspec) }
@@ -86,7 +96,7 @@ module Ci
it "returns builds if only has a list of branches including specified" do
config = YAML.dump({
before_script: ["pwd"],
- rspec: { script: "rspec", type: type, only: ["master", "deploy"] }
+ rspec: { script: "rspec", type: type, only: %w(master deploy) }
})
config_processor = GitlabCiYamlProcessor.new(config, path)
@@ -163,8 +173,8 @@ module Ci
it "returns build only for specified type" do
config = YAML.dump({
before_script: ["pwd"],
- rspec: { script: "rspec", type: "test", only: ["master", "deploy"] },
- staging: { script: "deploy", type: "deploy", only: ["master", "deploy"] },
+ rspec: { script: "rspec", type: "test", only: %w(master deploy) },
+ staging: { script: "deploy", type: "deploy", only: %w(master deploy) },
production: { script: "deploy", type: "deploy", only: ["master@path", "deploy"] },
})
@@ -242,7 +252,7 @@ module Ci
it "does not return builds if except has a list of branches including specified" do
config = YAML.dump({
before_script: ["pwd"],
- rspec: { script: "rspec", type: type, except: ["master", "deploy"] }
+ rspec: { script: "rspec", type: type, except: %w(master deploy) }
})
config_processor = GitlabCiYamlProcessor.new(config, path)
@@ -570,7 +580,7 @@ module Ci
context 'when syntax is incorrect' do
context 'when variables defined but invalid' do
let(:variables) do
- [ 'VAR1', 'value1', 'VAR2', 'value2' ]
+ %w(VAR1 value1 VAR2 value2)
end
it 'raises error' do
@@ -899,7 +909,7 @@ module Ci
end
context 'dependencies to builds' do
- let(:dependencies) { ['build1', 'build2'] }
+ let(:dependencies) { %w(build1 build2) }
it { expect { subject }.not_to raise_error }
end
@@ -1205,7 +1215,7 @@ EOT
end
it "returns errors if job stage is not a defined stage" do
- config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", type: "acceptance" } })
+ config = YAML.dump({ types: %w(build test), rspec: { script: "test", type: "acceptance" } })
expect do
GitlabCiYamlProcessor.new(config, path)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: stage parameter should be build, test")
@@ -1247,42 +1257,42 @@ EOT
end
it "returns errors if job artifacts:name is not an a string" do
- config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { name: 1 } } })
+ config = YAML.dump({ types: %w(build test), rspec: { script: "test", artifacts: { name: 1 } } })
expect do
GitlabCiYamlProcessor.new(config)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:artifacts name should be a string")
end
it "returns errors if job artifacts:when is not an a predefined value" do
- config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { when: 1 } } })
+ config = YAML.dump({ types: %w(build test), rspec: { script: "test", artifacts: { when: 1 } } })
expect do
GitlabCiYamlProcessor.new(config)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:artifacts when should be on_success, on_failure or always")
end
it "returns errors if job artifacts:expire_in is not an a string" do
- config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { expire_in: 1 } } })
+ config = YAML.dump({ types: %w(build test), rspec: { script: "test", artifacts: { expire_in: 1 } } })
expect do
GitlabCiYamlProcessor.new(config)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:artifacts expire in should be a duration")
end
it "returns errors if job artifacts:expire_in is not an a valid duration" do
- config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { expire_in: "7 elephants" } } })
+ config = YAML.dump({ types: %w(build test), rspec: { script: "test", artifacts: { expire_in: "7 elephants" } } })
expect do
GitlabCiYamlProcessor.new(config)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:artifacts expire in should be a duration")
end
it "returns errors if job artifacts:untracked is not an array of strings" do
- config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { untracked: "string" } } })
+ config = YAML.dump({ types: %w(build test), rspec: { script: "test", artifacts: { untracked: "string" } } })
expect do
GitlabCiYamlProcessor.new(config)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:artifacts untracked should be a boolean value")
end
it "returns errors if job artifacts:paths is not an array of strings" do
- config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { paths: "string" } } })
+ config = YAML.dump({ types: %w(build test), rspec: { script: "test", artifacts: { paths: "string" } } })
expect do
GitlabCiYamlProcessor.new(config)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:artifacts paths should be an array of strings")
@@ -1310,28 +1320,28 @@ EOT
end
it "returns errors if job cache:key is not an a string" do
- config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", cache: { key: 1 } } })
+ config = YAML.dump({ types: %w(build test), rspec: { script: "test", cache: { key: 1 } } })
expect do
GitlabCiYamlProcessor.new(config)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:cache:key config should be a string or symbol")
end
it "returns errors if job cache:untracked is not an array of strings" do
- config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", cache: { untracked: "string" } } })
+ config = YAML.dump({ types: %w(build test), rspec: { script: "test", cache: { untracked: "string" } } })
expect do
GitlabCiYamlProcessor.new(config)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:cache:untracked config should be a boolean value")
end
it "returns errors if job cache:paths is not an array of strings" do
- config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", cache: { paths: "string" } } })
+ config = YAML.dump({ types: %w(build test), rspec: { script: "test", cache: { paths: "string" } } })
expect do
GitlabCiYamlProcessor.new(config)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:cache:paths config should be an array of strings")
end
it "returns errors if job dependencies is not an array of strings" do
- config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", dependencies: "string" } })
+ config = YAML.dump({ types: %w(build test), rspec: { script: "test", dependencies: "string" } })
expect do
GitlabCiYamlProcessor.new(config)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec dependencies should be an array of strings")
diff --git a/spec/lib/constraints/project_url_constrainer_spec.rb b/spec/lib/constraints/project_url_constrainer_spec.rb
index a5251e9a8c2..4f25ad88960 100644
--- a/spec/lib/constraints/project_url_constrainer_spec.rb
+++ b/spec/lib/constraints/project_url_constrainer_spec.rb
@@ -6,7 +6,7 @@ describe ProjectUrlConstrainer, lib: true do
describe '#matches?' do
context 'valid request' do
- let(:request) { build_request(namespace.path, project.path) }
+ let(:request) { build_request(namespace.full_path, project.path) }
it { expect(subject.matches?(request)).to be_truthy }
end
@@ -19,7 +19,7 @@ describe ProjectUrlConstrainer, lib: true do
end
context "project id ending with .git" do
- let(:request) { build_request(namespace.path, project.path + '.git') }
+ let(:request) { build_request(namespace.full_path, project.path + '.git') }
it { expect(subject.matches?(request)).to be_falsey }
end
diff --git a/spec/lib/expand_variables_spec.rb b/spec/lib/expand_variables_spec.rb
index 90bc7dad379..730ca1f7c0a 100644
--- a/spec/lib/expand_variables_spec.rb
+++ b/spec/lib/expand_variables_spec.rb
@@ -7,58 +7,49 @@ describe ExpandVariables do
tests = [
{ value: 'key',
result: 'key',
- variables: []
- },
+ variables: [] },
{ value: 'key$variable',
result: 'key',
- variables: []
- },
+ variables: [] },
{ value: 'key$variable',
result: 'keyvalue',
variables: [
{ key: 'variable', value: 'value' }
- ]
- },
+ ] },
{ value: 'key${variable}',
result: 'keyvalue',
variables: [
{ key: 'variable', value: 'value' }
- ]
- },
+ ] },
{ value: 'key$variable$variable2',
result: 'keyvalueresult',
variables: [
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'result' },
- ]
- },
+ ] },
{ value: 'key${variable}${variable2}',
result: 'keyvalueresult',
variables: [
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'result' }
- ]
- },
+ ] },
{ value: 'key$variable2$variable',
result: 'keyresultvalue',
variables: [
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'result' },
- ]
- },
+ ] },
{ value: 'key${variable2}${variable}',
result: 'keyresultvalue',
variables: [
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'result' }
- ]
- },
+ ] },
{ value: 'review/$CI_BUILD_REF_NAME',
result: 'review/feature/add-review-apps',
variables: [
{ key: 'CI_BUILD_REF_NAME', value: 'feature/add-review-apps' }
- ]
- },
+ ] },
]
tests.each do |test|
diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb
index 29c07655ae8..33ab005667a 100644
--- a/spec/lib/extracts_path_spec.rb
+++ b/spec/lib/extracts_path_spec.rb
@@ -177,12 +177,12 @@ describe ExtractsPath, lib: true do
it "extracts a valid commit SHA" do
expect(extract_ref('f4b14494ef6abf3d144c28e4af0c20143383e062/CHANGELOG')).to eq(
- ['f4b14494ef6abf3d144c28e4af0c20143383e062', 'CHANGELOG']
+ %w(f4b14494ef6abf3d144c28e4af0c20143383e062 CHANGELOG)
)
end
it "falls back to a primitive split for an invalid ref" do
- expect(extract_ref('stable/CHANGELOG')).to eq(['stable', 'CHANGELOG'])
+ expect(extract_ref('stable/CHANGELOG')).to eq(%w(stable CHANGELOG))
end
end
end
diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb
index ba199917f5c..bca57105d1d 100644
--- a/spec/lib/gitlab/asciidoc_spec.rb
+++ b/spec/lib/gitlab/asciidoc_spec.rb
@@ -41,6 +41,29 @@ module Gitlab
render(input, context, asciidoc_opts)
end
end
+
+ context "XSS" do
+ links = {
+ 'links' => {
+ input: 'link:mylink"onmouseover="alert(1)[Click Here]',
+ output: "<div>\n<p><a href=\"mylink\">Click Here</a></p>\n</div>"
+ },
+ 'images' => {
+ input: 'image:https://localhost.com/image.png[Alt text" onerror="alert(7)]',
+ output: "<div>\n<p><span><img src=\"https://localhost.com/image.png\" alt=\"Alt text\"></span></p>\n</div>"
+ },
+ 'pre' => {
+ input: '```mypre"><script>alert(3)</script>',
+ output: "<div>\n<div>\n<pre lang=\"mypre\">\"&gt;<code></code></pre>\n</div>\n</div>"
+ }
+ }
+
+ links.each do |name, data|
+ it "does not convert dangerous #{name} into HTML" do
+ expect(render(data[:input], context)).to eql data[:output]
+ end
+ end
+ end
end
def render(*args)
diff --git a/spec/lib/gitlab/badge/shared/metadata.rb b/spec/lib/gitlab/badge/shared/metadata.rb
index 0cf18514251..63c7ca5a915 100644
--- a/spec/lib/gitlab/badge/shared/metadata.rb
+++ b/spec/lib/gitlab/badge/shared/metadata.rb
@@ -18,4 +18,14 @@ shared_examples 'badge metadata' do
it { is_expected.to include metadata.image_url }
it { is_expected.to include metadata.link_url }
end
+
+ describe '#to_asciidoc' do
+ subject { metadata.to_asciidoc }
+
+ it { is_expected.to include metadata.image_url }
+ it { is_expected.to include metadata.link_url }
+ it { is_expected.to include 'image:' }
+ it { is_expected.to include 'link=' }
+ it { is_expected.to include 'title=' }
+ end
end
diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
index 0a2fe5af2c3..a7ee7f53a6b 100644
--- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
@@ -87,10 +87,10 @@ describe Gitlab::BitbucketImport::Importer, lib: true do
body: issues_statuses_sample_data.to_json)
stub_request(:get, "https://api.bitbucket.org/2.0/repositories/namespace/repo?pagelen=50&sort=created_on").
- with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer', 'User-Agent' => 'Faraday v0.9.2' }).
- to_return(status: 200,
- body: "",
- headers: {})
+ with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer', 'User-Agent' => 'Faraday v0.9.2' }).
+ to_return(status: 200,
+ body: "",
+ headers: {})
sample_issues_statuses.each_with_index do |issue, index|
stub_request(
diff --git a/spec/lib/gitlab/chat_commands/presenters/issue_show_spec.rb b/spec/lib/gitlab/chat_commands/presenters/issue_show_spec.rb
index 5b678d31fce..3916fc704a4 100644
--- a/spec/lib/gitlab/chat_commands/presenters/issue_show_spec.rb
+++ b/spec/lib/gitlab/chat_commands/presenters/issue_show_spec.rb
@@ -26,6 +26,21 @@ describe Gitlab::ChatCommands::Presenters::IssueShow do
end
end
+ context 'with labels' do
+ let(:label) { create(:label, project: project, title: 'mep') }
+ let(:label1) { create(:label, project: project, title: 'mop') }
+
+ before do
+ issue.labels << [label, label1]
+ end
+
+ it 'shows the labels' do
+ labels = attachment[:fields].find { |f| f[:title] == 'Labels' }
+
+ expect(labels[:value]).to eq("mep, mop")
+ end
+ end
+
context 'confidential issue' do
let(:issue) { create(:issue, project: project) }
diff --git a/spec/lib/gitlab/ci/config/entry/commands_spec.rb b/spec/lib/gitlab/ci/config/entry/commands_spec.rb
index b8b0825a1c7..afa4a089418 100644
--- a/spec/lib/gitlab/ci/config/entry/commands_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/commands_spec.rb
@@ -4,7 +4,7 @@ describe Gitlab::Ci::Config::Entry::Commands do
let(:entry) { described_class.new(config) }
context 'when entry config value is an array' do
- let(:config) { ['ls', 'pwd'] }
+ let(:config) { %w(ls pwd) }
describe '#value' do
it 'returns array of strings' do
diff --git a/spec/lib/gitlab/ci/config/entry/factory_spec.rb b/spec/lib/gitlab/ci/config/entry/factory_spec.rb
index 00dad5d9591..3395b3c645b 100644
--- a/spec/lib/gitlab/ci/config/entry/factory_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/factory_spec.rb
@@ -8,20 +8,20 @@ describe Gitlab::Ci::Config::Entry::Factory do
context 'when setting a concrete value' do
it 'creates entry with valid value' do
entry = factory
- .value(['ls', 'pwd'])
+ .value(%w(ls pwd))
.create!
- expect(entry.value).to eq ['ls', 'pwd']
+ expect(entry.value).to eq %w(ls pwd)
end
context 'when setting description' do
it 'creates entry with description' do
entry = factory
- .value(['ls', 'pwd'])
+ .value(%w(ls pwd))
.with(description: 'test description')
.create!
- expect(entry.value).to eq ['ls', 'pwd']
+ expect(entry.value).to eq %w(ls pwd)
expect(entry.description).to eq 'test description'
end
end
@@ -29,7 +29,7 @@ describe Gitlab::Ci::Config::Entry::Factory do
context 'when setting key' do
it 'creates entry with custom key' do
entry = factory
- .value(['ls', 'pwd'])
+ .value(%w(ls pwd))
.with(key: 'test key')
.create!
diff --git a/spec/lib/gitlab/ci/config/entry/global_spec.rb b/spec/lib/gitlab/ci/config/entry/global_spec.rb
index 432a99dce33..ebd80ac5e1d 100644
--- a/spec/lib/gitlab/ci/config/entry/global_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/global_spec.rb
@@ -21,12 +21,12 @@ describe Gitlab::Ci::Config::Entry::Global do
context 'when configuration is valid' do
context 'when some entries defined' do
let(:hash) do
- { before_script: ['ls', 'pwd'],
+ { before_script: %w(ls pwd),
image: 'ruby:2.2',
services: ['postgres:9.1', 'mysql:5.5'],
variables: { VAR: 'value' },
after_script: ['make clean'],
- stages: ['build', 'pages'],
+ stages: %w(build pages),
cache: { key: 'k', untracked: true, paths: ['public/'] },
rspec: { script: %w[rspec ls] },
spinach: { before_script: [], variables: {}, script: 'spinach' } }
@@ -89,7 +89,7 @@ describe Gitlab::Ci::Config::Entry::Global do
describe '#before_script_value' do
it 'returns correct script' do
- expect(global.before_script_value).to eq ['ls', 'pwd']
+ expect(global.before_script_value).to eq %w(ls pwd)
end
end
@@ -126,7 +126,7 @@ describe Gitlab::Ci::Config::Entry::Global do
context 'when deprecated types key defined' do
let(:hash) do
- { types: ['test', 'deploy'],
+ { types: %w(test deploy),
rspec: { script: 'rspec' } }
end
@@ -148,7 +148,7 @@ describe Gitlab::Ci::Config::Entry::Global do
expect(global.jobs_value).to eq(
rspec: { name: :rspec,
script: %w[rspec ls],
- before_script: ['ls', 'pwd'],
+ before_script: %w(ls pwd),
commands: "ls\npwd\nrspec\nls",
image: 'ruby:2.2',
services: ['postgres:9.1', 'mysql:5.5'],
diff --git a/spec/lib/gitlab/ci/config/entry/key_spec.rb b/spec/lib/gitlab/ci/config/entry/key_spec.rb
index a55e5b4b8ac..0dd36fe1f44 100644
--- a/spec/lib/gitlab/ci/config/entry/key_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/key_spec.rb
@@ -21,7 +21,7 @@ describe Gitlab::Ci::Config::Entry::Key do
end
context 'when entry value is not correct' do
- let(:config) { [ 'incorrect' ] }
+ let(:config) { ['incorrect'] }
describe '#errors' do
it 'saves errors' do
diff --git a/spec/lib/gitlab/ci/config/entry/paths_spec.rb b/spec/lib/gitlab/ci/config/entry/paths_spec.rb
index e60c9aaf661..1d9c5ddee9b 100644
--- a/spec/lib/gitlab/ci/config/entry/paths_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/paths_spec.rb
@@ -21,7 +21,7 @@ describe Gitlab::Ci::Config::Entry::Paths do
end
context 'when entry value is not valid' do
- let(:config) { [ 1 ] }
+ let(:config) { [1] }
describe '#errors' do
it 'saves errors' do
diff --git a/spec/lib/gitlab/ci/config/entry/script_spec.rb b/spec/lib/gitlab/ci/config/entry/script_spec.rb
index aa99cee2690..069eaa26422 100644
--- a/spec/lib/gitlab/ci/config/entry/script_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/script_spec.rb
@@ -5,7 +5,7 @@ describe Gitlab::Ci::Config::Entry::Script do
describe 'validations' do
context 'when entry config value is correct' do
- let(:config) { ['ls', 'pwd'] }
+ let(:config) { %w(ls pwd) }
describe '#value' do
it 'returns array of strings' do
diff --git a/spec/lib/gitlab/ci/config/entry/variables_spec.rb b/spec/lib/gitlab/ci/config/entry/variables_spec.rb
index 58327d08904..f15f02f403e 100644
--- a/spec/lib/gitlab/ci/config/entry/variables_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/variables_spec.rb
@@ -29,7 +29,7 @@ describe Gitlab::Ci::Config::Entry::Variables do
end
context 'when entry value is not correct' do
- let(:config) { [ :VAR, 'test' ] }
+ let(:config) { [:VAR, 'test'] }
describe '#errors' do
it 'saves errors' do
diff --git a/spec/lib/gitlab/conflict/file_spec.rb b/spec/lib/gitlab/conflict/file_spec.rb
index fbf679c5215..780ac0ad97e 100644
--- a/spec/lib/gitlab/conflict/file_spec.rb
+++ b/spec/lib/gitlab/conflict/file_spec.rb
@@ -44,7 +44,7 @@ describe Gitlab::Conflict::File, lib: true do
it 'returns a file containing only the chosen parts of the resolved sections' do
expect(resolved_lines.chunk { |line| line.type || 'both' }.map(&:first)).
- to eq(['both', 'new', 'both', 'old', 'both', 'new', 'both'])
+ to eq(%w(both new both old both new both))
end
end
@@ -123,7 +123,7 @@ describe Gitlab::Conflict::File, lib: true do
it 'sets conflict to true for sections with only changed lines' do
conflict_file.sections.select { |section| section[:conflict] }.each do |section|
section[:lines].each do |line|
- expect(line.type).to be_in(['new', 'old'])
+ expect(line.type).to be_in(%w(new old))
end
end
end
@@ -251,7 +251,7 @@ FILE
describe '#as_json' do
it 'includes the blob path for the file' do
expect(conflict_file.as_json[:blob_path]).
- to eq("/#{project.namespace.to_param}/#{merge_request.project.to_param}/blob/#{our_commit.oid}/files/ruby/regex.rb")
+ to eq("/#{project.full_path}/blob/#{our_commit.oid}/files/ruby/regex.rb")
end
it 'includes the blob icon for the file' do
diff --git a/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
new file mode 100644
index 00000000000..c455cd9b942
--- /dev/null
+++ b/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+describe Gitlab::CycleAnalytics::BaseEventFetcher do
+ let(:max_events) { 2 }
+ let(:project) { create(:project) }
+ let(:user) { create(:user, :admin) }
+ let(:start_time_attrs) { Issue.arel_table[:created_at] }
+ let(:end_time_attrs) { [Issue::Metrics.arel_table[:first_associated_with_milestone_at]] }
+ let(:options) do
+ { start_time_attrs: start_time_attrs,
+ end_time_attrs: end_time_attrs,
+ from: 30.days.ago }
+ end
+
+ subject do
+ described_class.new(project: project,
+ stage: :issue,
+ options: options).fetch
+ end
+
+ before do
+ allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return(Issue.all)
+ allow_any_instance_of(Gitlab::CycleAnalytics::BaseEventFetcher).to receive(:serialize) do |event|
+ event
+ end
+
+ stub_const('Gitlab::CycleAnalytics::BaseEventFetcher::MAX_EVENTS', max_events)
+
+ setup_events(count: 3)
+ end
+
+ it 'limits the rows to the max number' do
+ expect(subject.count).to eq(max_events)
+ end
+
+ def setup_events(count:)
+ count.times do
+ issue = create(:issue, project: project, created_at: 2.days.ago)
+ milestone = create(:milestone, project: project)
+
+ issue.update(milestone: milestone)
+ create_merge_request_closing_issue(issue)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/data_builder/build_spec.rb b/spec/lib/gitlab/data_builder/build_spec.rb
index 6c71e98066b..91c43f2bdc0 100644
--- a/spec/lib/gitlab/data_builder/build_spec.rb
+++ b/spec/lib/gitlab/data_builder/build_spec.rb
@@ -17,5 +17,31 @@ describe Gitlab::DataBuilder::Build do
it { expect(data[:build_allow_failure]).to eq(false) }
it { expect(data[:project_id]).to eq(build.project.id) }
it { expect(data[:project_name]).to eq(build.project.name_with_namespace) }
+
+ context 'commit author_url' do
+ context 'when no commit present' do
+ let(:build) { create(:ci_build) }
+
+ it 'sets to mailing address of git_author_email' do
+ expect(data[:commit][:author_url]).to eq("mailto:#{build.pipeline.git_author_email}")
+ end
+ end
+
+ context 'when commit present but has no author' do
+ let(:build) { create(:ci_build, :with_commit) }
+
+ it 'sets to mailing address of git_author_email' do
+ expect(data[:commit][:author_url]).to eq("mailto:#{build.pipeline.git_author_email}")
+ end
+ end
+
+ context 'when commit and author are present' do
+ let(:build) { create(:ci_build, :with_commit_and_author) }
+
+ it 'sets to GitLab user url' do
+ expect(data[:commit][:author_url]).to eq(Gitlab::Routing.url_helpers.user_url(username: build.commit.author.username))
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 7fd25b9e5bf..e007044868c 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -12,15 +12,14 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
describe '#add_concurrent_index' do
context 'outside a transaction' do
before do
- expect(model).to receive(:transaction_open?).and_return(false)
-
- unless Gitlab::Database.postgresql?
- allow_any_instance_of(Gitlab::Database::MigrationHelpers).to receive(:disable_statement_timeout)
- end
+ allow(model).to receive(:transaction_open?).and_return(false)
end
context 'using PostgreSQL' do
- before { expect(Gitlab::Database).to receive(:postgresql?).and_return(true) }
+ before do
+ allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
+ allow(model).to receive(:disable_statement_timeout)
+ end
it 'creates the index concurrently' do
expect(model).to receive(:add_index).
@@ -59,6 +58,81 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
end
end
+ describe '#add_concurrent_foreign_key' do
+ context 'inside a transaction' do
+ it 'raises an error' do
+ expect(model).to receive(:transaction_open?).and_return(true)
+
+ expect do
+ model.add_concurrent_foreign_key(:projects, :users, column: :user_id)
+ end.to raise_error(RuntimeError)
+ end
+ end
+
+ context 'outside a transaction' do
+ before do
+ allow(model).to receive(:transaction_open?).and_return(false)
+ end
+
+ context 'using MySQL' do
+ it 'creates a regular foreign key' do
+ allow(Gitlab::Database).to receive(:mysql?).and_return(true)
+
+ expect(model).to receive(:add_foreign_key).
+ with(:projects, :users, column: :user_id, on_delete: :cascade)
+
+ model.add_concurrent_foreign_key(:projects, :users, column: :user_id)
+ end
+ end
+
+ context 'using PostgreSQL' do
+ before do
+ allow(Gitlab::Database).to receive(:mysql?).and_return(false)
+ end
+
+ it 'creates a concurrent foreign key' do
+ expect(model).to receive(:disable_statement_timeout)
+ expect(model).to receive(:execute).ordered.with(/NOT VALID/)
+ expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
+
+ model.add_concurrent_foreign_key(:projects, :users, column: :user_id)
+ end
+ end
+ end
+ end
+
+ describe '#concurrent_foreign_key_name' do
+ it 'returns the name for a foreign key' do
+ name = model.concurrent_foreign_key_name(:this_is_a_very_long_table_name,
+ :with_a_very_long_column_name)
+
+ expect(name).to be_an_instance_of(String)
+ expect(name.length).to eq(13)
+ end
+ end
+
+ describe '#disable_statement_timeout' do
+ context 'using PostgreSQL' do
+ it 'disables statement timeouts' do
+ expect(Gitlab::Database).to receive(:postgresql?).and_return(true)
+
+ expect(model).to receive(:execute).with('SET statement_timeout TO 0')
+
+ model.disable_statement_timeout
+ end
+ end
+
+ context 'using MySQL' do
+ it 'does nothing' do
+ expect(Gitlab::Database).to receive(:postgresql?).and_return(false)
+
+ expect(model).not_to receive(:execute)
+
+ model.disable_statement_timeout
+ end
+ end
+ end
+
describe '#update_column_in_batches' do
before do
create_list(:empty_project, 5)
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
index b142b3a2781..edd01d032c8 100644
--- a/spec/lib/gitlab/database_spec.rb
+++ b/spec/lib/gitlab/database_spec.rb
@@ -5,6 +5,12 @@ class MigrationTest
end
describe Gitlab::Database, lib: true do
+ describe '.adapter_name' do
+ it 'returns the name of the adapter' do
+ expect(described_class.adapter_name).to be_an_instance_of(String)
+ end
+ end
+
# These are just simple smoke tests to check if the methods work (regardless
# of what they may return).
describe '.mysql?' do
@@ -71,6 +77,69 @@ describe Gitlab::Database, lib: true do
end
end
+ describe '.with_connection_pool' do
+ it 'creates a new connection pool and disconnect it after used' do
+ closed_pool = nil
+
+ described_class.with_connection_pool(1) do |pool|
+ pool.with_connection do |connection|
+ connection.execute('SELECT 1 AS value')
+ end
+
+ expect(pool).to be_connected
+
+ closed_pool = pool
+ end
+
+ expect(closed_pool).not_to be_connected
+ end
+
+ it 'disconnects the pool even an exception was raised' do
+ error = Class.new(RuntimeError)
+ closed_pool = nil
+
+ begin
+ described_class.with_connection_pool(1) do |pool|
+ pool.with_connection do |connection|
+ connection.execute('SELECT 1 AS value')
+ end
+
+ closed_pool = pool
+
+ raise error.new('boom')
+ end
+ rescue error
+ end
+
+ expect(closed_pool).not_to be_connected
+ end
+ end
+
+ describe '.create_connection_pool' do
+ it 'creates a new connection pool with specific pool size' do
+ pool = described_class.create_connection_pool(5)
+
+ begin
+ expect(pool)
+ .to be_kind_of(ActiveRecord::ConnectionAdapters::ConnectionPool)
+
+ expect(pool.spec.config[:pool]).to eq(5)
+ ensure
+ pool.disconnect!
+ end
+ end
+
+ it 'allows setting of a custom hostname' do
+ pool = described_class.create_connection_pool(5, '127.0.0.1')
+
+ begin
+ expect(pool.spec.config[:host]).to eq('127.0.0.1')
+ ensure
+ pool.disconnect!
+ end
+ end
+ end
+
describe '#true_value' do
it 'returns correct value for PostgreSQL' do
expect(described_class).to receive(:postgresql?).and_return(true)
diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb
index 5893485634d..0e9309d278e 100644
--- a/spec/lib/gitlab/diff/highlight_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_spec.rb
@@ -53,21 +53,21 @@ describe Gitlab::Diff::Highlight, lib: true do
end
it 'marks unchanged lines' do
- code = %Q{ def popen(cmd, path=nil)}
+ code = %q{ def popen(cmd, path=nil)}
expect(subject[2].text).to eq(code)
expect(subject[2].text).not_to be_html_safe
end
it 'marks removed lines' do
- code = %Q{- raise "System commands must be given as an array of strings"}
+ code = %q{- raise "System commands must be given as an array of strings"}
expect(subject[4].text).to eq(code)
expect(subject[4].text).not_to be_html_safe
end
it 'marks added lines' do
- code = %Q{+ raise <span class='idiff left right'>RuntimeError, </span>&quot;System commands must be given as an array of strings&quot;}
+ code = %q{+ raise <span class='idiff left right'>RuntimeError, </span>&quot;System commands must be given as an array of strings&quot;}
expect(subject[5].text).to eq(code)
expect(subject[5].text).to be_html_safe
diff --git a/spec/lib/gitlab/diff/position_tracer_spec.rb b/spec/lib/gitlab/diff/position_tracer_spec.rb
index 8e3e4034c8f..994995b57b8 100644
--- a/spec/lib/gitlab/diff/position_tracer_spec.rb
+++ b/spec/lib/gitlab/diff/position_tracer_spec.rb
@@ -122,7 +122,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
end
def delete_file(branch_name, file_name)
- Files::DeleteService.new(
+ Files::DestroyService.new(
project,
current_user,
start_branch: branch_name,
diff --git a/spec/lib/gitlab/git/blob_snippet_spec.rb b/spec/lib/gitlab/git/blob_snippet_spec.rb
index 79b1311ac91..17d6be470ac 100644
--- a/spec/lib/gitlab/git/blob_snippet_spec.rb
+++ b/spec/lib/gitlab/git/blob_snippet_spec.rb
@@ -11,7 +11,7 @@ describe Gitlab::Git::BlobSnippet, seed_helper: true do
end
context 'present lines' do
- let(:snippet) { Gitlab::Git::BlobSnippet.new('master', ['wow', 'much'], 1, 'wow.rb') }
+ let(:snippet) { Gitlab::Git::BlobSnippet.new('master', %w(wow much), 1, 'wow.rb') }
it { expect(snippet.data).to eq("wow\nmuch") }
end
diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb
index 84f79ec2391..0c321f0343c 100644
--- a/spec/lib/gitlab/git/blob_spec.rb
+++ b/spec/lib/gitlab/git/blob_spec.rb
@@ -292,7 +292,7 @@ describe Gitlab::Git::Blob, seed_helper: true do
it 'should preserve file modes with commit' do
commit_options[:file][:path] = 'files/executables/ls'
- entry = Gitlab::Git::Blob::find_entry_by_path(repository, commit.tree.oid, commit_options[:file][:path])
+ entry = Gitlab::Git::Blob.find_entry_by_path(repository, commit.tree.oid, commit_options[:file][:path])
expect(entry[:filemode]).to eq(0100755)
end
end
diff --git a/spec/lib/gitlab/git/diff_collection_spec.rb b/spec/lib/gitlab/git/diff_collection_spec.rb
index 4fa72c565ae..47bdd7310d5 100644
--- a/spec/lib/gitlab/git/diff_collection_spec.rb
+++ b/spec/lib/gitlab/git/diff_collection_spec.rb
@@ -365,7 +365,7 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
end
context 'when go over safe limits on files' do
- let(:iterator) { [ fake_diff(1, 1) ] * 4 }
+ let(:iterator) { [fake_diff(1, 1)] * 4 }
before(:each) do
stub_const('Gitlab::Git::DiffCollection::DEFAULT_LIMITS', { max_files: 2, max_lines: max_lines })
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index a55bd4387e0..d37890de9ae 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -199,7 +199,9 @@ describe Gitlab::GitAccess, lib: true do
def stub_git_hooks
# Running the `pre-receive` hook is expensive, and not necessary for this test.
- allow_any_instance_of(GitHooksService).to receive(:execute).and_yield
+ allow_any_instance_of(GitHooksService).to receive(:execute) do |service, &block|
+ block.call(service)
+ end
end
def merge_into_protected_branch
@@ -232,11 +234,18 @@ describe Gitlab::GitAccess, lib: true do
else
project.team << [user, role]
end
+ end
+
+ permissions_matrix[role].each do |action, allowed|
+ context action do
+ subject { access.send(:check_push_access!, changes[action]) }
- permissions_matrix[role].each do |action, allowed|
- context action do
- subject { access.send(:check_push_access!, changes[action]) }
- it { expect(subject.allowed?).to allowed ? be_truthy : be_falsey }
+ it do
+ if allowed
+ expect { subject }.not_to raise_error
+ else
+ expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError)
+ end
end
end
end
@@ -301,7 +310,7 @@ describe Gitlab::GitAccess, lib: true do
}
}
- [['feature', 'exact'], ['feat*', 'wildcard']].each do |protected_branch_name, protected_branch_type|
+ [%w(feature exact), ['feat*', 'wildcard']].each do |protected_branch_name, protected_branch_type|
context do
before { create(:protected_branch, name: protected_branch_name, project: project) }
diff --git a/spec/lib/gitlab/git_spec.rb b/spec/lib/gitlab/git_spec.rb
index 219198eff60..8eaf7aac264 100644
--- a/spec/lib/gitlab/git_spec.rb
+++ b/spec/lib/gitlab/git_spec.rb
@@ -19,7 +19,7 @@ describe Gitlab::Git, lib: true do
describe 'committer_hash' do
it "returns a hash containing the given email and name" do
- committer_hash = Gitlab::Git::committer_hash(email: committer_email, name: committer_name)
+ committer_hash = Gitlab::Git.committer_hash(email: committer_email, name: committer_name)
expect(committer_hash[:email]).to eq(committer_email)
expect(committer_hash[:name]).to eq(committer_name)
@@ -28,7 +28,7 @@ describe Gitlab::Git, lib: true do
context 'when email is nil' do
it "returns nil" do
- committer_hash = Gitlab::Git::committer_hash(email: nil, name: committer_name)
+ committer_hash = Gitlab::Git.committer_hash(email: nil, name: committer_name)
expect(committer_hash).to be_nil
end
@@ -36,7 +36,7 @@ describe Gitlab::Git, lib: true do
context 'when name is nil' do
it "returns nil" do
- committer_hash = Gitlab::Git::committer_hash(email: committer_email, name: nil)
+ committer_hash = Gitlab::Git.committer_hash(email: committer_email, name: nil)
expect(committer_hash).to be_nil
end
diff --git a/spec/lib/gitlab/github_import/comment_formatter_spec.rb b/spec/lib/gitlab/github_import/comment_formatter_spec.rb
index e6e33d3686a..cc38872e426 100644
--- a/spec/lib/gitlab/github_import/comment_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/comment_formatter_spec.rb
@@ -1,8 +1,9 @@
require 'spec_helper'
describe Gitlab::GithubImport::CommentFormatter, lib: true do
+ let(:client) { double }
let(:project) { create(:empty_project) }
- let(:octocat) { double(id: 123456, login: 'octocat') }
+ let(:octocat) { double(id: 123456, login: 'octocat', email: 'octocat@example.com') }
let(:created_at) { DateTime.strptime('2013-04-10T20:09:31Z') }
let(:updated_at) { DateTime.strptime('2014-03-03T18:58:10Z') }
let(:base) do
@@ -16,7 +17,11 @@ describe Gitlab::GithubImport::CommentFormatter, lib: true do
}
end
- subject(:comment) { described_class.new(project, raw)}
+ subject(:comment) { described_class.new(project, raw, client) }
+
+ before do
+ allow(client).to receive(:user).and_return(octocat)
+ end
describe '#attributes' do
context 'when do not reference a portion of the diff' do
@@ -69,8 +74,15 @@ describe Gitlab::GithubImport::CommentFormatter, lib: true do
context 'when author is a GitLab user' do
let(:raw) { double(base.merge(user: octocat)) }
- it 'returns GitLab user id as author_id' do
+ it 'returns GitLab user id associated with GitHub id as author_id' do
gl_user = create(:omniauth_user, extern_uid: octocat.id, provider: 'github')
+
+ expect(comment.attributes.fetch(:author_id)).to eq gl_user.id
+ end
+
+ it 'returns GitLab user id associated with GitHub email as author_id' do
+ gl_user = create(:user, email: octocat.email)
+
expect(comment.attributes.fetch(:author_id)).to eq gl_user.id
end
diff --git a/spec/lib/gitlab/github_import/importer_spec.rb b/spec/lib/gitlab/github_import/importer_spec.rb
index afd78abdc9b..33d83d6d2f1 100644
--- a/spec/lib/gitlab/github_import/importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer_spec.rb
@@ -44,6 +44,7 @@ describe Gitlab::GithubImport::Importer, lib: true do
allow_any_instance_of(Octokit::Client).to receive(:rate_limit!).and_raise(Octokit::NotFound)
allow_any_instance_of(Gitlab::Shell).to receive(:import_repository).and_raise(Gitlab::Shell::Error)
+ allow_any_instance_of(Octokit::Client).to receive(:user).and_return(octocat)
allow_any_instance_of(Octokit::Client).to receive(:labels).and_return([label1, label2])
allow_any_instance_of(Octokit::Client).to receive(:milestones).and_return([milestone, milestone])
allow_any_instance_of(Octokit::Client).to receive(:issues).and_return([issue1, issue2])
@@ -53,7 +54,8 @@ describe Gitlab::GithubImport::Importer, lib: true do
allow_any_instance_of(Octokit::Client).to receive(:last_response).and_return(double(rels: { next: nil }))
allow_any_instance_of(Octokit::Client).to receive(:releases).and_return([release1, release2])
end
- let(:octocat) { double(id: 123456, login: 'octocat') }
+
+ let(:octocat) { double(id: 123456, login: 'octocat', email: 'octocat@example.com') }
let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
let(:label1) do
@@ -125,6 +127,7 @@ describe Gitlab::GithubImport::Importer, lib: true do
)
end
+ let!(:user) { create(:user, email: octocat.email) }
let(:repository) { double(id: 1, fork: false) }
let(:source_sha) { create(:commit, project: project).id }
let(:source_branch) { double(ref: 'feature', repo: repository, sha: source_sha) }
diff --git a/spec/lib/gitlab/github_import/issue_formatter_spec.rb b/spec/lib/gitlab/github_import/issue_formatter_spec.rb
index eec1fabab54..f34d09f2c1d 100644
--- a/spec/lib/gitlab/github_import/issue_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/issue_formatter_spec.rb
@@ -1,8 +1,9 @@
require 'spec_helper'
describe Gitlab::GithubImport::IssueFormatter, lib: true do
+ let(:client) { double }
let!(:project) { create(:empty_project, namespace: create(:namespace, path: 'octocat')) }
- let(:octocat) { double(id: 123456, login: 'octocat') }
+ let(:octocat) { double(id: 123456, login: 'octocat', email: 'octocat@example.com') }
let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
@@ -23,7 +24,11 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
}
end
- subject(:issue) { described_class.new(project, raw_data) }
+ subject(:issue) { described_class.new(project, raw_data, client) }
+
+ before do
+ allow(client).to receive(:user).and_return(octocat)
+ end
shared_examples 'Gitlab::GithubImport::IssueFormatter#attributes' do
context 'when issue is open' do
@@ -75,11 +80,17 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
expect(issue.attributes.fetch(:assignee_id)).to be_nil
end
- it 'returns GitLab user id as assignee_id when is a GitLab user' do
+ it 'returns GitLab user id associated with GitHub id as assignee_id' do
gl_user = create(:omniauth_user, extern_uid: octocat.id, provider: 'github')
expect(issue.attributes.fetch(:assignee_id)).to eq gl_user.id
end
+
+ it 'returns GitLab user id associated with GitHub email as assignee_id' do
+ gl_user = create(:user, email: octocat.email)
+
+ expect(issue.attributes.fetch(:assignee_id)).to eq gl_user.id
+ end
end
context 'when it has a milestone' do
@@ -100,16 +111,22 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
context 'when author is a GitLab user' do
let(:raw_data) { double(base_data.merge(user: octocat)) }
- it 'returns project#creator_id as author_id when is not a GitLab user' do
+ it 'returns project creator_id as author_id when is not a GitLab user' do
expect(issue.attributes.fetch(:author_id)).to eq project.creator_id
end
- it 'returns GitLab user id as author_id when is a GitLab user' do
+ it 'returns GitLab user id associated with GitHub id as author_id' do
gl_user = create(:omniauth_user, extern_uid: octocat.id, provider: 'github')
expect(issue.attributes.fetch(:author_id)).to eq gl_user.id
end
+ it 'returns GitLab user id associated with GitHub email as author_id' do
+ gl_user = create(:user, email: octocat.email)
+
+ expect(issue.attributes.fetch(:author_id)).to eq gl_user.id
+ end
+
it 'returns description without created at tag line' do
create(:omniauth_user, extern_uid: octocat.id, provider: 'github')
diff --git a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
index 90947ff4707..e46be18aa99 100644
--- a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
@@ -1,6 +1,7 @@
require 'spec_helper'
describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
+ let(:client) { double }
let(:project) { create(:project, :repository) }
let(:source_sha) { create(:commit, project: project).id }
let(:target_sha) { create(:commit, project: project, git_commit: RepoHelpers.another_sample_commit).id }
@@ -10,7 +11,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
let(:target_repo) { repository }
let(:target_branch) { double(ref: 'master', repo: target_repo, sha: target_sha) }
let(:removed_branch) { double(ref: 'removed-branch', repo: source_repo, sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b') }
- let(:octocat) { double(id: 123456, login: 'octocat') }
+ let(:octocat) { double(id: 123456, login: 'octocat', email: 'octocat@example.com') }
let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
let(:base_data) do
@@ -32,7 +33,11 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
}
end
- subject(:pull_request) { described_class.new(project, raw_data) }
+ subject(:pull_request) { described_class.new(project, raw_data, client) }
+
+ before do
+ allow(client).to receive(:user).and_return(octocat)
+ end
shared_examples 'Gitlab::GithubImport::PullRequestFormatter#attributes' do
context 'when pull request is open' do
@@ -121,26 +126,38 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
expect(pull_request.attributes.fetch(:assignee_id)).to be_nil
end
- it 'returns GitLab user id as assignee_id when is a GitLab user' do
+ it 'returns GitLab user id associated with GitHub id as assignee_id' do
gl_user = create(:omniauth_user, extern_uid: octocat.id, provider: 'github')
expect(pull_request.attributes.fetch(:assignee_id)).to eq gl_user.id
end
+
+ it 'returns GitLab user id associated with GitHub email as assignee_id' do
+ gl_user = create(:user, email: octocat.email)
+
+ expect(pull_request.attributes.fetch(:assignee_id)).to eq gl_user.id
+ end
end
context 'when author is a GitLab user' do
let(:raw_data) { double(base_data.merge(user: octocat)) }
- it 'returns project#creator_id as author_id when is not a GitLab user' do
+ it 'returns project creator_id as author_id when is not a GitLab user' do
expect(pull_request.attributes.fetch(:author_id)).to eq project.creator_id
end
- it 'returns GitLab user id as author_id when is a GitLab user' do
+ it 'returns GitLab user id associated with GitHub id as author_id' do
gl_user = create(:omniauth_user, extern_uid: octocat.id, provider: 'github')
expect(pull_request.attributes.fetch(:author_id)).to eq gl_user.id
end
+ it 'returns GitLab user id associated with GitHub email as author_id' do
+ gl_user = create(:user, email: octocat.email)
+
+ expect(pull_request.attributes.fetch(:author_id)).to eq gl_user.id
+ end
+
it 'returns description without created at tag line' do
create(:omniauth_user, extern_uid: octocat.id, provider: 'github')
diff --git a/spec/lib/gitlab/github_import/user_formatter_spec.rb b/spec/lib/gitlab/github_import/user_formatter_spec.rb
new file mode 100644
index 00000000000..db792233657
--- /dev/null
+++ b/spec/lib/gitlab/github_import/user_formatter_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+describe Gitlab::GithubImport::UserFormatter, lib: true do
+ let(:client) { double }
+ let(:octocat) { double(id: 123456, login: 'octocat', email: 'octocat@example.com') }
+
+ subject(:user) { described_class.new(client, octocat) }
+
+ before do
+ allow(client).to receive(:user).and_return(octocat)
+ end
+
+ describe '#gitlab_id' do
+ context 'when GitHub user is a GitLab user' do
+ it 'return GitLab user id when user associated their account with GitHub' do
+ gl_user = create(:omniauth_user, extern_uid: octocat.id, provider: 'github')
+
+ expect(user.gitlab_id).to eq gl_user.id
+ end
+
+ it 'returns GitLab user id when user primary email matches GitHub email' do
+ gl_user = create(:user, email: octocat.email)
+
+ expect(user.gitlab_id).to eq gl_user.id
+ end
+
+ it 'returns GitLab user id when any of user linked emails matches GitHub email' do
+ gl_user = create(:user, email: 'johndoe@example.com')
+ create(:email, user: gl_user, email: octocat.email)
+
+ expect(user.gitlab_id).to eq gl_user.id
+ end
+ end
+
+ it 'returns nil when GitHub user is not a GitLab user' do
+ expect(user.gitlab_id).to be_nil
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/attribute_configuration_spec.rb b/spec/lib/gitlab/import_export/attribute_configuration_spec.rb
index ea65a5dfed1..e24d070706a 100644
--- a/spec/lib/gitlab/import_export/attribute_configuration_spec.rb
+++ b/spec/lib/gitlab/import_export/attribute_configuration_spec.rb
@@ -17,7 +17,7 @@ describe 'Import/Export attribute configuration', lib: true do
# Remove duplicated or add missing models
# - project is not part of the tree, so it has to be added manually.
# - milestone, labels have both singular and plural versions in the tree, so remove the duplicates.
- names.flatten.uniq - ['milestones', 'labels'] + ['project']
+ names.flatten.uniq - %w(milestones labels) + ['project']
end
let(:safe_attributes_file) { 'spec/lib/gitlab/import_export/safe_model_attributes.yml' }
diff --git a/spec/lib/gitlab/import_export/avatar_saver_spec.rb b/spec/lib/gitlab/import_export/avatar_saver_spec.rb
index d6ee94442cb..579a31ead58 100644
--- a/spec/lib/gitlab/import_export/avatar_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/avatar_saver_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Gitlab::ImportExport::AvatarSaver, lib: true do
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') }
- let(:export_path) { "#{Dir::tmpdir}/project_tree_saver_spec" }
+ let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:project_with_avatar) { create(:empty_project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
let(:project) { create(:empty_project) }
diff --git a/spec/lib/gitlab/import_export/file_importer_spec.rb b/spec/lib/gitlab/import_export/file_importer_spec.rb
index a88ddd17aca..b88b9c18c15 100644
--- a/spec/lib/gitlab/import_export/file_importer_spec.rb
+++ b/spec/lib/gitlab/import_export/file_importer_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Gitlab::ImportExport::FileImporter, lib: true do
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') }
- let(:export_path) { "#{Dir::tmpdir}/file_importer_spec" }
+ let(:export_path) { "#{Dir.tmpdir}/file_importer_spec" }
let(:valid_file) { "#{shared.export_path}/valid.json" }
let(:symlink_file) { "#{shared.export_path}/invalid.json" }
let(:subfolder_symlink_file) { "#{shared.export_path}/subfolder/invalid.json" }
diff --git a/spec/lib/gitlab/import_export/import_export_spec.rb b/spec/lib/gitlab/import_export/import_export_spec.rb
index 53f7d244d88..f3fd0d82875 100644
--- a/spec/lib/gitlab/import_export/import_export_spec.rb
+++ b/spec/lib/gitlab/import_export/import_export_spec.rb
@@ -2,14 +2,15 @@ require 'spec_helper'
describe Gitlab::ImportExport, services: true do
describe 'export filename' do
- let(:project) { create(:empty_project, :public, path: 'project-path') }
+ let(:group) { create(:group, :nested) }
+ let(:project) { create(:empty_project, :public, path: 'project-path', namespace: group) }
it 'contains the project path' do
expect(described_class.export_filename(project: project)).to include(project.path)
end
it 'contains the namespace path' do
- expect(described_class.export_filename(project: project)).to include(project.namespace.path)
+ expect(described_class.export_filename(project: project)).to include(project.namespace.full_path.tr('/', '_'))
end
it 'does not go over a certain length' do
diff --git a/spec/lib/gitlab/import_export/model_configuration_spec.rb b/spec/lib/gitlab/import_export/model_configuration_spec.rb
index 9b492d1b9c7..2ede5cdd2ad 100644
--- a/spec/lib/gitlab/import_export/model_configuration_spec.rb
+++ b/spec/lib/gitlab/import_export/model_configuration_spec.rb
@@ -14,7 +14,7 @@ describe 'Import/Export model configuration', lib: true do
# - project is not part of the tree, so it has to be added manually.
# - milestone, labels have both singular and plural versions in the tree, so remove the duplicates.
# - User, Author... Models we do not care about for checking models
- names.flatten.uniq - ['milestones', 'labels', 'user', 'author'] + ['project']
+ names.flatten.uniq - %w(milestones labels user author) + ['project']
end
let(:all_models_yml) { 'spec/lib/gitlab/import_export/all_models.yml' }
diff --git a/spec/lib/gitlab/import_export/project.light.json b/spec/lib/gitlab/import_export/project.light.json
new file mode 100644
index 00000000000..a78836c3c34
--- /dev/null
+++ b/spec/lib/gitlab/import_export/project.light.json
@@ -0,0 +1,48 @@
+{
+ "description": "Nisi et repellendus ut enim quo accusamus vel magnam.",
+ "visibility_level": 10,
+ "archived": false,
+ "labels": [
+ {
+ "id": 2,
+ "title": "test2",
+ "color": "#428bca",
+ "project_id": 8,
+ "created_at": "2016-07-22T08:55:44.161Z",
+ "updated_at": "2016-07-22T08:55:44.161Z",
+ "template": false,
+ "description": "",
+ "type": "ProjectLabel",
+ "priorities": [
+ ]
+ },
+ {
+ "id": 3,
+ "title": "test3",
+ "color": "#428bca",
+ "group_id": 8,
+ "created_at": "2016-07-22T08:55:44.161Z",
+ "updated_at": "2016-07-22T08:55:44.161Z",
+ "template": false,
+ "description": "",
+ "project_id": null,
+ "type": "GroupLabel",
+ "priorities": [
+ {
+ "id": 1,
+ "project_id": 5,
+ "label_id": 1,
+ "priority": 1,
+ "created_at": "2016-10-18T09:35:43.338Z",
+ "updated_at": "2016-10-18T09:35:43.338Z"
+ }
+ ]
+ }
+ ],
+ "snippets": [
+
+ ],
+ "hooks": [
+
+ ]
+} \ No newline at end of file
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index 0af13ba8e47..f4a21c24fa1 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -3,24 +3,24 @@ include ImportExport::CommonUtil
describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
describe 'restore project tree' do
- let(:user) { create(:user) }
- let(:namespace) { create(:namespace, owner: user) }
- let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: "", project_path: 'path') }
- let!(:project) { create(:empty_project, :builds_disabled, :issues_disabled, name: 'project', path: 'project') }
- let(:project_tree_restorer) { described_class.new(user: user, shared: shared, project: project) }
- let(:restored_project_json) { project_tree_restorer.restore }
+ before(:context) do
+ @user = create(:user)
- before do
- allow(shared).to receive(:export_path).and_return('spec/lib/gitlab/import_export/')
+ RSpec::Mocks.with_temporary_scope do
+ @shared = Gitlab::ImportExport::Shared.new(relative_path: "", project_path: 'path')
+ allow(@shared).to receive(:export_path).and_return('spec/lib/gitlab/import_export/')
+ @project = create(:empty_project, :builds_disabled, :issues_disabled, name: 'project', path: 'project')
+ project_tree_restorer = described_class.new(user: @user, shared: @shared, project: @project)
+ @restored_project_json = project_tree_restorer.restore
+ end
end
context 'JSON' do
it 'restores models based on JSON' do
- expect(restored_project_json).to be true
+ expect(@restored_project_json).to be true
end
it 'restore correct project features' do
- restored_project_json
project = Project.find_by_path('project')
expect(project.project_feature.issues_access_level).to eq(ProjectFeature::DISABLED)
@@ -31,62 +31,42 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
end
it 'has the same label associated to two issues' do
- restored_project_json
-
expect(ProjectLabel.find_by_title('test2').issues.count).to eq(2)
end
it 'has milestones associated to two separate issues' do
- restored_project_json
-
expect(Milestone.find_by_description('test milestone').issues.count).to eq(2)
end
it 'creates a valid pipeline note' do
- restored_project_json
-
expect(Ci::Pipeline.first.notes).not_to be_empty
end
it 'restores pipelines with missing ref' do
- restored_project_json
-
expect(Ci::Pipeline.where(ref: nil)).not_to be_empty
end
it 'restores the correct event with symbolised data' do
- restored_project_json
-
expect(Event.where.not(data: nil).first.data[:ref]).not_to be_empty
end
it 'preserves updated_at on issues' do
- restored_project_json
-
issue = Issue.where(description: 'Aliquam enim illo et possimus.').first
expect(issue.reload.updated_at.to_s).to eq('2016-06-14 15:02:47 UTC')
end
it 'contains the merge access levels on a protected branch' do
- restored_project_json
-
expect(ProtectedBranch.first.merge_access_levels).not_to be_empty
end
it 'contains the push access levels on a protected branch' do
- restored_project_json
-
expect(ProtectedBranch.first.push_access_levels).not_to be_empty
end
context 'event at forth level of the tree' do
let(:event) { Event.where(title: 'test levels').first }
- before do
- restored_project_json
- end
-
it 'restores the event' do
expect(event).not_to be_nil
end
@@ -99,77 +79,40 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
it 'has the correct data for merge request st_diffs' do
# makes sure we are renaming the custom method +utf8_st_diffs+ into +st_diffs+
- expect { restored_project_json }.to change(MergeRequestDiff.where.not(st_diffs: nil), :count).by(9)
+ expect(MergeRequestDiff.where.not(st_diffs: nil).count).to eq(9)
end
it 'has labels associated to label links, associated to issues' do
- restored_project_json
-
expect(Label.first.label_links.first.target).not_to be_nil
end
it 'has project labels' do
- restored_project_json
-
expect(ProjectLabel.count).to eq(2)
end
it 'has no group labels' do
- restored_project_json
-
expect(GroupLabel.count).to eq(0)
end
- context 'with group' do
- let!(:project) do
- create(:empty_project,
- :builds_disabled,
- :issues_disabled,
- name: 'project',
- path: 'project',
- group: create(:group))
- end
-
- it 'has group labels' do
- restored_project_json
-
- expect(GroupLabel.count).to eq(1)
- end
-
- it 'has label priorities' do
- restored_project_json
-
- expect(GroupLabel.first.priorities).not_to be_empty
- end
- end
-
it 'has a project feature' do
- restored_project_json
-
- expect(project.project_feature).not_to be_nil
+ expect(@project.project_feature).not_to be_nil
end
it 'restores the correct service' do
- restored_project_json
-
expect(CustomIssueTrackerService.first).not_to be_nil
end
context 'Merge requests' do
- before do
- restored_project_json
- end
-
it 'always has the new project as a target' do
- expect(MergeRequest.find_by_title('MR1').target_project).to eq(project)
+ expect(MergeRequest.find_by_title('MR1').target_project).to eq(@project)
end
it 'has the same source project as originally if source/target are the same' do
- expect(MergeRequest.find_by_title('MR1').source_project).to eq(project)
+ expect(MergeRequest.find_by_title('MR1').source_project).to eq(@project)
end
it 'has the new project as target if source/target differ' do
- expect(MergeRequest.find_by_title('MR2').target_project).to eq(project)
+ expect(MergeRequest.find_by_title('MR2').target_project).to eq(@project)
end
it 'has no source if source/target differ' do
@@ -177,39 +120,71 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
end
end
- context 'project.json file access check' do
- it 'does not read a symlink' do
- Dir.mktmpdir do |tmpdir|
- setup_symlink(tmpdir, 'project.json')
- allow(shared).to receive(:export_path).and_call_original
-
- restored_project_json
+ context 'tokens are regenerated' do
+ it 'has a new CI trigger token' do
+ expect(Ci::Trigger.where(token: 'cdbfasdf44a5958c83654733449e585')).to be_empty
+ end
- expect(shared.errors.first).not_to include('test')
- end
+ it 'has a new CI build token' do
+ expect(Ci::Build.where(token: 'abcd')).to be_empty
end
end
+ end
+ end
- context 'when there is an existing build with build token' do
- it 'restores project json correctly' do
- create(:ci_build, token: 'abcd')
+ context 'Light JSON' do
+ let(:user) { create(:user) }
+ let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: "", project_path: 'path') }
+ let!(:project) { create(:empty_project, :builds_disabled, :issues_disabled, name: 'project', path: 'project') }
+ let(:project_tree_restorer) { described_class.new(user: user, shared: shared, project: project) }
+ let(:restored_project_json) { project_tree_restorer.restore }
- expect(restored_project_json).to be true
- end
- end
+ before do
+ allow(ImportExport).to receive(:project_filename).and_return('project.light.json')
+ allow(shared).to receive(:export_path).and_return('spec/lib/gitlab/import_export/')
+ end
+
+ context 'project.json file access check' do
+ it 'does not read a symlink' do
+ Dir.mktmpdir do |tmpdir|
+ setup_symlink(tmpdir, 'project.json')
+ allow(shared).to receive(:export_path).and_call_original
- context 'tokens are regenerated' do
- before do
restored_project_json
- end
- it 'has a new CI trigger token' do
- expect(Ci::Trigger.where(token: 'cdbfasdf44a5958c83654733449e585')).to be_empty
+ expect(shared.errors.first).not_to include('test')
end
+ end
+ end
- it 'has a new CI build token' do
- expect(Ci::Build.where(token: 'abcd')).to be_empty
- end
+ context 'when there is an existing build with build token' do
+ it 'restores project json correctly' do
+ create(:ci_build, token: 'abcd')
+
+ expect(restored_project_json).to be true
+ end
+ end
+
+ context 'with group' do
+ let!(:project) do
+ create(:empty_project,
+ :builds_disabled,
+ :issues_disabled,
+ name: 'project',
+ path: 'project',
+ group: create(:group))
+ end
+
+ before do
+ restored_project_json
+ end
+
+ it 'has group labels' do
+ expect(GroupLabel.count).to eq(1)
+ end
+
+ it 'has label priorities' do
+ expect(GroupLabel.first.priorities).not_to be_empty
end
end
end
diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
index 3628adefc0c..012c22ec5ad 100644
--- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
@@ -4,7 +4,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
describe 'saves the project tree into a json object' do
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.path_with_namespace) }
let(:project_tree_saver) { described_class.new(project: project, current_user: user, shared: shared) }
- let(:export_path) { "#{Dir::tmpdir}/project_tree_saver_spec" }
+ let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:user) { create(:user) }
let(:project) { setup_project }
@@ -114,7 +114,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
it 'has project and group labels' do
label_types = saved_project_json['issues'].first['label_links'].map { |link| link['label']['type'] }
- expect(label_types).to match_array(['ProjectLabel', 'GroupLabel'])
+ expect(label_types).to match_array(%w(ProjectLabel GroupLabel))
end
it 'has priorities associated to labels' do
diff --git a/spec/lib/gitlab/import_export/repo_bundler_spec.rb b/spec/lib/gitlab/import_export/repo_bundler_spec.rb
index d39ea60ff7f..a7f4e11271e 100644
--- a/spec/lib/gitlab/import_export/repo_bundler_spec.rb
+++ b/spec/lib/gitlab/import_export/repo_bundler_spec.rb
@@ -4,7 +4,7 @@ describe Gitlab::ImportExport::RepoSaver, services: true do
describe 'bundle a project Git repo' do
let(:user) { create(:user) }
let!(:project) { create(:empty_project, :public, name: 'searchable_project') }
- let(:export_path) { "#{Dir::tmpdir}/project_tree_saver_spec" }
+ let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.path_with_namespace) }
let(:bundler) { described_class.new(project: project, shared: shared) }
diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
new file mode 100644
index 00000000000..168a59e5139
--- /dev/null
+++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
@@ -0,0 +1,40 @@
+require 'spec_helper'
+
+describe Gitlab::ImportExport::RepoRestorer, services: true do
+ describe 'bundle a project Git repo' do
+ let(:user) { create(:user) }
+ let!(:project_with_repo) { create(:project, :test_repo, name: 'test-repo-restorer', path: 'test-repo-restorer') }
+ let!(:project) { create(:empty_project) }
+ let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
+ let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.path_with_namespace) }
+ let(:bundler) { Gitlab::ImportExport::RepoSaver.new(project: project_with_repo, shared: shared) }
+ let(:bundle_path) { File.join(shared.export_path, Gitlab::ImportExport.project_bundle_filename) }
+ let(:restorer) do
+ described_class.new(path_to_bundle: bundle_path,
+ shared: shared,
+ project: project)
+ end
+
+ before do
+ allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
+
+ bundler.save
+ end
+
+ after do
+ FileUtils.rm_rf(export_path)
+ FileUtils.rm_rf(project_with_repo.repository.path_to_repo)
+ FileUtils.rm_rf(project.repository.path_to_repo)
+ end
+
+ it 'restores the repo successfully' do
+ expect(restorer.restore).to be true
+ end
+
+ it 'has the webhooks' do
+ restorer.restore
+
+ expect(Gitlab::Git::Hook.new('post-receive', project.repository.path_to_repo)).to exist
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/wiki_repo_bundler_spec.rb b/spec/lib/gitlab/import_export/wiki_repo_bundler_spec.rb
index 47d5d2fc150..071e5fac3f0 100644
--- a/spec/lib/gitlab/import_export/wiki_repo_bundler_spec.rb
+++ b/spec/lib/gitlab/import_export/wiki_repo_bundler_spec.rb
@@ -4,7 +4,7 @@ describe Gitlab::ImportExport::WikiRepoSaver, services: true do
describe 'bundle a wiki Git repo' do
let(:user) { create(:user) }
let!(:project) { create(:empty_project, :public, name: 'searchable_project') }
- let(:export_path) { "#{Dir::tmpdir}/project_tree_saver_spec" }
+ let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.path_with_namespace) }
let(:wiki_bundler) { described_class.new(project: project, shared: shared) }
let!(:project_wiki) { ProjectWiki.new(project, user) }
diff --git a/spec/lib/gitlab/import_sources_spec.rb b/spec/lib/gitlab/import_sources_spec.rb
index 8cea38e9ff8..b3b5e5e7e33 100644
--- a/spec/lib/gitlab/import_sources_spec.rb
+++ b/spec/lib/gitlab/import_sources_spec.rb
@@ -22,16 +22,16 @@ describe Gitlab::ImportSources do
describe '.values' do
it 'returns an array' do
expected =
- [
- 'github',
- 'bitbucket',
- 'gitlab',
- 'google_code',
- 'fogbugz',
- 'git',
- 'gitlab_project',
- 'gitea'
- ]
+ %w(
+ github
+ bitbucket
+ gitlab
+ google_code
+ fogbugz
+ git
+ gitlab_project
+ gitea
+ )
expect(described_class.values).to eq(expected)
end
@@ -40,15 +40,15 @@ describe Gitlab::ImportSources do
describe '.importer_names' do
it 'returns an array of importer names' do
expected =
- [
- 'github',
- 'bitbucket',
- 'gitlab',
- 'google_code',
- 'fogbugz',
- 'gitlab_project',
- 'gitea'
- ]
+ %w(
+ github
+ bitbucket
+ gitlab
+ google_code
+ fogbugz
+ gitlab_project
+ gitea
+ )
expect(described_class.importer_names).to eq(expected)
end
diff --git a/spec/lib/gitlab/kubernetes_spec.rb b/spec/lib/gitlab/kubernetes_spec.rb
index c9bd52a3b8f..91f9d06b85a 100644
--- a/spec/lib/gitlab/kubernetes_spec.rb
+++ b/spec/lib/gitlab/kubernetes_spec.rb
@@ -9,7 +9,7 @@ describe Gitlab::Kubernetes do
let(:pod_name) { 'pod1' }
let(:container_name) { 'container1' }
- subject(:result) { URI::parse(container_exec_url(api_url, namespace, pod_name, container_name)) }
+ subject(:result) { URI.parse(container_exec_url(api_url, namespace, pod_name, container_name)) }
it { expect(result.scheme).to eq('wss') }
it { expect(result.host).to eq('example.com') }
diff --git a/spec/lib/gitlab/ldap/auth_hash_spec.rb b/spec/lib/gitlab/ldap/auth_hash_spec.rb
index 69c49051156..7a2f774b948 100644
--- a/spec/lib/gitlab/ldap/auth_hash_spec.rb
+++ b/spec/lib/gitlab/ldap/auth_hash_spec.rb
@@ -44,7 +44,7 @@ describe Gitlab::LDAP::AuthHash, lib: true do
context "with overridden attributes" do
let(:attributes) do
{
- 'username' => ['mail', 'email'],
+ 'username' => %w(mail email),
'name' => 'fullName'
}
end
diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb
index 89790c9e1af..2f3bd4393b7 100644
--- a/spec/lib/gitlab/ldap/user_spec.rb
+++ b/spec/lib/gitlab/ldap/user_spec.rb
@@ -95,10 +95,10 @@ describe Gitlab::LDAP::User, lib: true do
it 'maintains an identity per provider' do
existing_user = create(:omniauth_user, email: 'john@example.com', provider: 'twitter')
- expect(existing_user.identities.count).to eql(1)
+ expect(existing_user.identities.count).to be(1)
ldap_user.save
- expect(ldap_user.gl_user.identities.count).to eql(2)
+ expect(ldap_user.gl_user.identities.count).to be(2)
# Expect that find_by provider only returns a single instance of an identity and not an Enumerable
expect(ldap_user.gl_user.identities.find_by(provider: 'twitter')).to be_instance_of Identity
diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb
index d88bcae41fb..a986cb520fb 100644
--- a/spec/lib/gitlab/metrics/instrumentation_spec.rb
+++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb
@@ -197,11 +197,13 @@ describe Gitlab::Metrics::Instrumentation do
@child1 = Class.new(@dummy) do
def self.child1_foo; end
+
def child1_bar; end
end
@child2 = Class.new(@child1) do
def self.child2_foo; end
+
def child2_bar; end
end
end
diff --git a/spec/lib/gitlab/metrics/method_call_spec.rb b/spec/lib/gitlab/metrics/method_call_spec.rb
index 8d05081eecb..a247f03b2da 100644
--- a/spec/lib/gitlab/metrics/method_call_spec.rb
+++ b/spec/lib/gitlab/metrics/method_call_spec.rb
@@ -23,7 +23,7 @@ describe Gitlab::Metrics::MethodCall do
expect(metric.values[:duration]).to be_a_kind_of(Numeric)
expect(metric.values[:cpu_duration]).to be_a_kind_of(Numeric)
- expect(metric.values[:call_count]).to an_instance_of(Fixnum)
+ expect(metric.values[:call_count]).to be_an(Integer)
expect(metric.tags).to eq({ method: 'Foo#bar' })
end
diff --git a/spec/lib/gitlab/metrics/metric_spec.rb b/spec/lib/gitlab/metrics/metric_spec.rb
index f26fca52c50..d240b8a01fd 100644
--- a/spec/lib/gitlab/metrics/metric_spec.rb
+++ b/spec/lib/gitlab/metrics/metric_spec.rb
@@ -62,7 +62,7 @@ describe Gitlab::Metrics::Metric do
end
it 'includes the timestamp' do
- expect(hash[:timestamp]).to be_an_instance_of(Fixnum)
+ expect(hash[:timestamp]).to be_an(Integer)
end
end
end
diff --git a/spec/lib/gitlab/metrics/system_spec.rb b/spec/lib/gitlab/metrics/system_spec.rb
index 9e2ea89a712..4d94d8705fb 100644
--- a/spec/lib/gitlab/metrics/system_spec.rb
+++ b/spec/lib/gitlab/metrics/system_spec.rb
@@ -29,19 +29,19 @@ describe Gitlab::Metrics::System do
describe '.cpu_time' do
it 'returns a Fixnum' do
- expect(described_class.cpu_time).to be_an_instance_of(Fixnum)
+ expect(described_class.cpu_time).to be_an(Integer)
end
end
describe '.real_time' do
it 'returns a Fixnum' do
- expect(described_class.real_time).to be_an_instance_of(Fixnum)
+ expect(described_class.real_time).to be_an(Integer)
end
end
describe '.monotonic_time' do
it 'returns a Fixnum' do
- expect(described_class.monotonic_time).to be_an_instance_of(Fixnum)
+ expect(described_class.monotonic_time).to be_an(Integer)
end
end
end
diff --git a/spec/lib/gitlab/metrics/transaction_spec.rb b/spec/lib/gitlab/metrics/transaction_spec.rb
index 3887c04c832..0c5a6246d85 100644
--- a/spec/lib/gitlab/metrics/transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/transaction_spec.rb
@@ -134,7 +134,7 @@ describe Gitlab::Metrics::Transaction do
series: 'rails_transactions',
tags: { action: 'Foo#bar' },
values: { duration: 0.0, allocated_memory: a_kind_of(Numeric) },
- timestamp: an_instance_of(Fixnum)
+ timestamp: a_kind_of(Integer)
}
expect(Gitlab::Metrics).to receive(:submit_metrics).
@@ -151,7 +151,7 @@ describe Gitlab::Metrics::Transaction do
series: 'events',
tags: { event: :meow },
values: { count: 1 },
- timestamp: an_instance_of(Fixnum)
+ timestamp: a_kind_of(Integer)
}
expect(Gitlab::Metrics).to receive(:submit_metrics).
diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb
index fc9e1cb430a..6c84a4c8b73 100644
--- a/spec/lib/gitlab/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/o_auth/user_spec.rb
@@ -148,12 +148,14 @@ describe Gitlab::OAuth::User, lib: true do
expect(gl_user).to be_valid
expect(gl_user.username).to eql uid
expect(gl_user.email).to eql 'johndoe@example.com'
- expect(gl_user.identities.length).to eql 2
+ expect(gl_user.identities.length).to be 2
identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
expect(identities_as_hash).to match_array(
- [ { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' },
+ [
+ { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' },
{ provider: 'twitter', extern_uid: uid }
- ])
+ ]
+ )
end
end
@@ -167,12 +169,14 @@ describe Gitlab::OAuth::User, lib: true do
expect(gl_user).to be_valid
expect(gl_user.username).to eql 'john'
expect(gl_user.email).to eql 'john@example.com'
- expect(gl_user.identities.length).to eql 2
+ expect(gl_user.identities.length).to be 2
identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
expect(identities_as_hash).to match_array(
- [ { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' },
+ [
+ { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' },
{ provider: 'twitter', extern_uid: uid }
- ])
+ ]
+ )
end
end
diff --git a/spec/lib/gitlab/optimistic_locking_spec.rb b/spec/lib/gitlab/optimistic_locking_spec.rb
index 498dc514c8c..acce2be93f2 100644
--- a/spec/lib/gitlab/optimistic_locking_spec.rb
+++ b/spec/lib/gitlab/optimistic_locking_spec.rb
@@ -1,10 +1,10 @@
require 'spec_helper'
describe Gitlab::OptimisticLocking, lib: true do
- describe '#retry_lock' do
- let!(:pipeline) { create(:ci_pipeline) }
- let!(:pipeline2) { Ci::Pipeline.find(pipeline.id) }
+ let!(:pipeline) { create(:ci_pipeline) }
+ let!(:pipeline2) { Ci::Pipeline.find(pipeline.id) }
+ describe '#retry_lock' do
it 'does not reload object if state changes' do
expect(pipeline).not_to receive(:reload)
expect(pipeline).to receive(:succeed).and_call_original
@@ -36,4 +36,17 @@ describe Gitlab::OptimisticLocking, lib: true do
end.to raise_error(ActiveRecord::StaleObjectError)
end
end
+
+ describe '#retry_optimistic_lock' do
+ context 'when locking module is mixed in' do
+ let(:unlockable) do
+ Class.new.include(described_class).new
+ end
+
+ it 'is an alias for retry_lock' do
+ expect(unlockable.method(:retry_optimistic_lock))
+ .to eq unlockable.method(:retry_lock)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/other_markup.rb b/spec/lib/gitlab/other_markup.rb
new file mode 100644
index 00000000000..8f5a353b381
--- /dev/null
+++ b/spec/lib/gitlab/other_markup.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+
+describe Gitlab::OtherMarkup, lib: true do
+ context "XSS Checks" do
+ links = {
+ 'links' => {
+ file: 'file.rdoc',
+ input: 'XSS[JaVaScriPt:alert(1)]',
+ output: '<p><a>XSS</a></p>'
+ }
+ }
+ links.each do |name, data|
+ it "does not convert dangerous #{name} into HTML" do
+ expect(render(data[:file], data[:input], context)).to eql data[:output]
+ end
+ end
+ end
+
+ def render(*args)
+ described_class.render(*args)
+ end
+end
diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index 6b689c41ef6..84cfd934fa0 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -42,14 +42,85 @@ describe Gitlab::ReferenceExtractor, lib: true do
> @offteam
})
+
expect(subject.users).to match_array([])
end
+ describe 'directly addressed users' do
+ before do
+ @u_foo = create(:user, username: 'foo')
+ @u_foo2 = create(:user, username: 'foo2')
+ @u_foo3 = create(:user, username: 'foo3')
+ @u_foo4 = create(:user, username: 'foo4')
+ @u_foo5 = create(:user, username: 'foo5')
+
+ @u_bar = create(:user, username: 'bar')
+ @u_bar2 = create(:user, username: 'bar2')
+ @u_bar3 = create(:user, username: 'bar3')
+ @u_bar4 = create(:user, username: 'bar4')
+
+ @u_tom = create(:user, username: 'tom')
+ @u_tom2 = create(:user, username: 'tom2')
+ end
+
+ context 'when a user is directly addressed' do
+ it 'accesses the user object which is mentioned in the beginning of the line' do
+ subject.analyze('@foo What do you think? cc: @bar, @tom')
+
+ expect(subject.directly_addressed_users).to match_array([@u_foo])
+ end
+
+ it "doesn't access the user object if it's not mentioned in the beginning of the line" do
+ subject.analyze('What do you think? cc: @bar')
+
+ expect(subject.directly_addressed_users).to be_empty
+ end
+ end
+
+ context 'when multiple users are addressed' do
+ it 'accesses the user objects which are mentioned in the beginning of the line' do
+ subject.analyze('@foo @bar What do you think? cc: @tom')
+
+ expect(subject.directly_addressed_users).to match_array([@u_foo, @u_bar])
+ end
+
+ it "doesn't access the user objects if they are not mentioned in the beginning of the line" do
+ subject.analyze('What do you think? cc: @foo @bar @tom')
+
+ expect(subject.directly_addressed_users).to be_empty
+ end
+ end
+
+ context 'when multiple users are addressed in different paragraphs' do
+ it 'accesses user objects which are mentioned in the beginning of each paragraph' do
+ subject.analyze <<-NOTE.strip_heredoc
+ @foo What do you think? cc: @tom
+
+ - @bar can you please have a look?
+
+ >>>
+ @foo2 what do you think? cc: @bar2
+ >>>
+
+ @foo3 @foo4 thank you!
+
+ > @foo5 well done!
+
+ 1. @bar3 Can you please check? cc: @tom2
+ 2. @bar4 What do you this of this MR?
+ NOTE
+
+ expect(subject.directly_addressed_users).to match_array([@u_foo, @u_foo3, @u_foo4])
+ end
+ end
+ end
+
it 'accesses valid issue objects' do
@i0 = create(:issue, project: project)
@i1 = create(:issue, project: project)
subject.analyze("#{@i0.to_reference}, #{@i1.to_reference}, and #{Issue.reference_prefix}999.")
+
expect(subject.issues).to match_array([@i0, @i1])
end
@@ -58,6 +129,7 @@ describe Gitlab::ReferenceExtractor, lib: true do
@m1 = create(:merge_request, source_project: project, target_project: project, source_branch: 'feature_conflict')
subject.analyze("!999, !#{@m1.iid}, and !#{@m0.iid}.")
+
expect(subject.merge_requests).to match_array([@m1, @m0])
end
@@ -67,6 +139,7 @@ describe Gitlab::ReferenceExtractor, lib: true do
@l2 = create(:label)
subject.analyze("~#{@l0.id}, ~999, ~#{@l2.id}, ~#{@l1.id}")
+
expect(subject.labels).to match_array([@l0, @l1])
end
@@ -76,6 +149,7 @@ describe Gitlab::ReferenceExtractor, lib: true do
@s2 = create(:project_snippet)
subject.analyze("$#{@s0.id}, $999, $#{@s2.id}, $#{@s1.id}")
+
expect(subject.snippets).to match_array([@s0, @s1])
end
@@ -127,6 +201,7 @@ describe Gitlab::ReferenceExtractor, lib: true do
it 'handles project issue references' do
subject.analyze("this refers issue #{issue.to_reference(project)}")
+
extracted = subject.issues
expect(extracted.size).to eq(1)
expect(extracted).to match_array([issue])
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index c78cd30157e..ba45e2d758c 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -2,47 +2,64 @@
require 'spec_helper'
describe Gitlab::Regex, lib: true do
- describe 'project path regex' do
- it { expect('gitlab-ce').to match(Gitlab::Regex.project_path_regex) }
- it { expect('gitlab_git').to match(Gitlab::Regex.project_path_regex) }
- it { expect('_underscore.js').to match(Gitlab::Regex.project_path_regex) }
- it { expect('100px.com').to match(Gitlab::Regex.project_path_regex) }
- it { expect('?gitlab').not_to match(Gitlab::Regex.project_path_regex) }
- it { expect('git lab').not_to match(Gitlab::Regex.project_path_regex) }
- it { expect('gitlab.git').not_to match(Gitlab::Regex.project_path_regex) }
+ describe '.project_path_regex' do
+ subject { described_class.project_path_regex }
+
+ it { is_expected.to match('gitlab-ce') }
+ it { is_expected.to match('gitlab_git') }
+ it { is_expected.to match('_underscore.js') }
+ it { is_expected.to match('100px.com') }
+ it { is_expected.not_to match('?gitlab') }
+ it { is_expected.not_to match('git lab') }
+ it { is_expected.not_to match('gitlab.git') }
end
- describe 'project name regex' do
- it { expect('gitlab-ce').to match(Gitlab::Regex.project_name_regex) }
- it { expect('GitLab CE').to match(Gitlab::Regex.project_name_regex) }
- it { expect('100 lines').to match(Gitlab::Regex.project_name_regex) }
- it { expect('gitlab.git').to match(Gitlab::Regex.project_name_regex) }
- it { expect('Český název').to match(Gitlab::Regex.project_name_regex) }
- it { expect('Dash – is this').to match(Gitlab::Regex.project_name_regex) }
- it { expect('?gitlab').not_to match(Gitlab::Regex.project_name_regex) }
+ describe '.project_name_regex' do
+ subject { described_class.project_name_regex }
+
+ it { is_expected.to match('gitlab-ce') }
+ it { is_expected.to match('GitLab CE') }
+ it { is_expected.to match('100 lines') }
+ it { is_expected.to match('gitlab.git') }
+ it { is_expected.to match('Český název') }
+ it { is_expected.to match('Dash – is this') }
+ it { is_expected.not_to match('?gitlab') }
end
- describe 'file name regex' do
- it { expect('foo@bar').to match(Gitlab::Regex.file_name_regex) }
+ describe '.file_name_regex' do
+ subject { described_class.file_name_regex }
+
+ it { is_expected.to match('foo@bar') }
end
- describe 'file path regex' do
- it { expect('foo@/bar').to match(Gitlab::Regex.file_path_regex) }
+ describe '.file_path_regex' do
+ subject { described_class.file_path_regex }
+
+ it { is_expected.to match('foo@/bar') }
end
- describe 'environment slug regex' do
- def be_matched
- match(Gitlab::Regex.environment_slug_regex)
- end
+ describe '.environment_slug_regex' do
+ subject { described_class.environment_slug_regex }
+
+ it { is_expected.to match('foo') }
+ it { is_expected.to match('foo-1') }
+ it { is_expected.not_to match('FOO') }
+ it { is_expected.not_to match('foo/1') }
+ it { is_expected.not_to match('foo.1') }
+ it { is_expected.not_to match('foo*1') }
+ it { is_expected.not_to match('9foo') }
+ it { is_expected.not_to match('foo-') }
+ end
- it { expect('foo').to be_matched }
- it { expect('foo-1').to be_matched }
+ describe 'FULL_NAMESPACE_REGEX_STR' do
+ subject { %r{\A#{Gitlab::Regex::FULL_NAMESPACE_REGEX_STR}\z} }
- it { expect('FOO').not_to be_matched }
- it { expect('foo/1').not_to be_matched }
- it { expect('foo.1').not_to be_matched }
- it { expect('foo*1').not_to be_matched }
- it { expect('9foo').not_to be_matched }
- it { expect('foo-').not_to be_matched }
+ it { is_expected.to match('gitlab.org') }
+ it { is_expected.to match('gitlab.org/gitlab-git') }
+ it { is_expected.not_to match('gitlab.org.') }
+ it { is_expected.not_to match('gitlab.org/') }
+ it { is_expected.not_to match('/gitlab.org') }
+ it { is_expected.not_to match('gitlab.git') }
+ it { is_expected.not_to match('gitlab git') }
end
end
diff --git a/spec/lib/gitlab/saml/user_spec.rb b/spec/lib/gitlab/saml/user_spec.rb
index 02c139f1a0d..4f6ef3c10fc 100644
--- a/spec/lib/gitlab/saml/user_spec.rb
+++ b/spec/lib/gitlab/saml/user_spec.rb
@@ -155,11 +155,10 @@ describe Gitlab::Saml::User, lib: true do
expect(gl_user).to be_valid
expect(gl_user.username).to eql uid
expect(gl_user.email).to eql 'john@mail.com'
- expect(gl_user.identities.length).to eql 2
+ expect(gl_user.identities.length).to be 2
identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
- expect(identities_as_hash).to match_array([ { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' },
- { provider: 'saml', extern_uid: uid }
- ])
+ expect(identities_as_hash).to match_array([{ provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' },
+ { provider: 'saml', extern_uid: uid }])
end
end
@@ -178,11 +177,10 @@ describe Gitlab::Saml::User, lib: true do
expect(gl_user).to be_valid
expect(gl_user.username).to eql 'john'
expect(gl_user.email).to eql 'john@mail.com'
- expect(gl_user.identities.length).to eql 2
+ expect(gl_user.identities.length).to be 2
identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
- expect(identities_as_hash).to match_array([ { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' },
- { provider: 'saml', extern_uid: uid }
- ])
+ expect(identities_as_hash).to match_array([{ provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' },
+ { provider: 'saml', extern_uid: uid }])
end
it 'saves successfully on subsequent tries, when both identities are present' do
@@ -204,11 +202,10 @@ describe Gitlab::Saml::User, lib: true do
local_gl_user = local_saml_user.gl_user
expect(local_gl_user).to be_valid
- expect(local_gl_user.identities.length).to eql 2
+ expect(local_gl_user.identities.length).to be 2
identities_as_hash = local_gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
- expect(identities_as_hash).to match_array([ { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' },
- { provider: 'saml', extern_uid: 'uid=user1,ou=People,dc=example' }
- ])
+ expect(identities_as_hash).to match_array([{ provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' },
+ { provider: 'saml', extern_uid: 'uid=user1,ou=People,dc=example' }])
end
end
end
diff --git a/spec/lib/gitlab/serialize/ci/variables_spec.rb b/spec/lib/gitlab/serializer/ci/variables_spec.rb
index 7ea74da5252..c4b7fda5dbb 100644
--- a/spec/lib/gitlab/serialize/ci/variables_spec.rb
+++ b/spec/lib/gitlab/serializer/ci/variables_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Serialize::Ci::Variables do
+describe Gitlab::Serializer::Ci::Variables do
subject do
described_class.load(described_class.dump(object))
end
@@ -13,6 +13,7 @@ describe Gitlab::Serialize::Ci::Variables do
it 'converts keys into strings' do
is_expected.to eq([
{ key: 'key', value: 'value', public: true },
- { key: 'wee', value: 1, public: false }])
+ { key: 'wee', value: 1, public: false }
+ ])
end
end
diff --git a/spec/lib/gitlab/serializer/pagination_spec.rb b/spec/lib/gitlab/serializer/pagination_spec.rb
new file mode 100644
index 00000000000..519eb1b274f
--- /dev/null
+++ b/spec/lib/gitlab/serializer/pagination_spec.rb
@@ -0,0 +1,49 @@
+require 'spec_helper'
+
+describe Gitlab::Serializer::Pagination do
+ let(:request) { spy('request') }
+ let(:response) { spy('response') }
+ let(:headers) { spy('headers') }
+
+ before do
+ allow(request).to receive(:query_parameters)
+ .and_return(params)
+
+ allow(response).to receive(:headers)
+ .and_return(headers)
+ end
+
+ let(:pagination) { described_class.new(request, response) }
+
+ describe '#paginate' do
+ subject { pagination.paginate(resource) }
+
+ let(:resource) { User.all }
+ let(:params) { { page: 1, per_page: 2 } }
+
+ context 'when a multiple resources are present in relation' do
+ before { create_list(:user, 3) }
+
+ it 'correctly paginates the resource' do
+ expect(subject.count).to be 2
+ end
+
+ it 'appends relevant headers' do
+ expect(headers).to receive(:[]=).with('X-Total', '3')
+ expect(headers).to receive(:[]=).with('X-Total-Pages', '2')
+ expect(headers).to receive(:[]=).with('X-Per-Page', '2')
+
+ subject
+ end
+ end
+
+ context 'when an invalid resource is about to be paginated' do
+ let(:resource) { create(:user) }
+
+ it 'raises error' do
+ expect { subject }.to raise_error(
+ described_class::InvalidResourceError)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/slash_commands/extractor_spec.rb b/spec/lib/gitlab/slash_commands/extractor_spec.rb
index 1e4954c4af8..d7f77486b3e 100644
--- a/spec/lib/gitlab/slash_commands/extractor_spec.rb
+++ b/spec/lib/gitlab/slash_commands/extractor_spec.rb
@@ -81,6 +81,14 @@ describe Gitlab::SlashCommands::Extractor do
let(:original_msg) { "/assign @joe\nworld" }
let(:final_msg) { "world" }
end
+
+ it 'allows slash in command arguments' do
+ msg = "/assign @joe / @jane\nworld"
+ msg, commands = extractor.extract_commands(msg)
+
+ expect(commands).to eq [['assign', '@joe / @jane']]
+ expect(msg).to eq 'world'
+ end
end
context 'in the middle of content' do
diff --git a/spec/lib/gitlab/template/issue_template_spec.rb b/spec/lib/gitlab/template/issue_template_spec.rb
index 1335a2b8f35..9213ced7b19 100644
--- a/spec/lib/gitlab/template/issue_template_spec.rb
+++ b/spec/lib/gitlab/template/issue_template_spec.rb
@@ -11,7 +11,8 @@ describe Gitlab::Template::IssueTemplate do
create_template: {
user: user,
access: Gitlab::Access::MASTER,
- path: 'issue_templates' })
+ path: 'issue_templates'
+ })
end
describe '.all' do
diff --git a/spec/lib/gitlab/template/merge_request_template_spec.rb b/spec/lib/gitlab/template/merge_request_template_spec.rb
index 320b870309a..77dd3079e22 100644
--- a/spec/lib/gitlab/template/merge_request_template_spec.rb
+++ b/spec/lib/gitlab/template/merge_request_template_spec.rb
@@ -11,7 +11,8 @@ describe Gitlab::Template::MergeRequestTemplate do
create_template: {
user: user,
access: Gitlab::Access::MASTER,
- path: 'merge_request_templates' })
+ path: 'merge_request_templates'
+ })
end
describe '.all' do
diff --git a/spec/lib/gitlab/themes_spec.rb b/spec/lib/gitlab/themes_spec.rb
deleted file mode 100644
index 7a140518dd2..00000000000
--- a/spec/lib/gitlab/themes_spec.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Themes, lib: true do
- describe '.body_classes' do
- it 'returns a space-separated list of class names' do
- css = described_class.body_classes
-
- expect(css).to include('ui_graphite')
- expect(css).to include(' ui_charcoal ')
- expect(css).to include(' ui_blue')
- end
- end
-
- describe '.by_id' do
- it 'returns a Theme by its ID' do
- expect(described_class.by_id(1).name).to eq 'Graphite'
- expect(described_class.by_id(6).name).to eq 'Blue'
- end
- end
-
- describe '.default' do
- it 'returns the default application theme' do
- allow(described_class).to receive(:default_id).and_return(2)
- expect(described_class.default.id).to eq 2
- end
-
- it 'prevents an infinite loop when configuration default is invalid' do
- default = described_class::APPLICATION_DEFAULT
- themes = described_class::THEMES
-
- config = double(default_theme: 0).as_null_object
- allow(Gitlab).to receive(:config).and_return(config)
- expect(described_class.default.id).to eq default
-
- config = double(default_theme: themes.size + 5).as_null_object
- allow(Gitlab).to receive(:config).and_return(config)
- expect(described_class.default.id).to eq default
- end
- end
-
- describe '.each' do
- it 'passes the block to the THEMES Array' do
- ids = []
- described_class.each { |theme| ids << theme.id }
- expect(ids).not_to be_empty
- end
- end
-end
diff --git a/spec/lib/gitlab/upgrader_spec.rb b/spec/lib/gitlab/upgrader_spec.rb
index edadab043d7..fcfd8d58b70 100644
--- a/spec/lib/gitlab/upgrader_spec.rb
+++ b/spec/lib/gitlab/upgrader_spec.rb
@@ -32,7 +32,8 @@ describe Gitlab::Upgrader, lib: true do
'43af3e65a486a9237f29f56d96c3b3da59c24ae0 refs/tags/v7.11.2',
'dac18e7728013a77410e926a1e64225703754a2d refs/tags/v7.11.2^{}',
'0bf21fd4b46c980c26fd8c90a14b86a4d90cc950 refs/tags/v7.9.4',
- 'b10de29edbaff7219547dc506cb1468ee35065c3 refs/tags/v7.9.4^{}'])
+ 'b10de29edbaff7219547dc506cb1468ee35065c3 refs/tags/v7.9.4^{}'
+ ])
expect(upgrader.latest_version_raw).to eq("v7.11.2")
end
end
diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb
index d5d87310874..56772409989 100644
--- a/spec/lib/gitlab/utils_spec.rb
+++ b/spec/lib/gitlab/utils_spec.rb
@@ -1,7 +1,5 @@
describe Gitlab::Utils, lib: true do
- def to_boolean(value)
- described_class.to_boolean(value)
- end
+ delegate :to_boolean, to: :described_class
describe '.to_boolean' do
it 'accepts booleans' do
diff --git a/spec/lib/mattermost/command_spec.rb b/spec/lib/mattermost/command_spec.rb
index 5ccf1100898..4b5938edeb9 100644
--- a/spec/lib/mattermost/command_spec.rb
+++ b/spec/lib/mattermost/command_spec.rb
@@ -13,8 +13,7 @@ describe Mattermost::Command do
describe '#create' do
let(:params) do
{ team_id: 'abc',
- trigger: 'gitlab'
- }
+ trigger: 'gitlab' }
end
subject { described_class.new(nil).create(params) }
@@ -24,7 +23,8 @@ describe Mattermost::Command do
stub_request(:post, 'http://mattermost.example.com/api/v3/teams/abc/commands/create').
with(body: {
team_id: 'abc',
- trigger: 'gitlab' }.to_json).
+ trigger: 'gitlab'
+ }.to_json).
to_return(
status: 200,
headers: { 'Content-Type' => 'application/json' },
diff --git a/spec/lib/mattermost/team_spec.rb b/spec/lib/mattermost/team_spec.rb
index 2d14be6bcc2..6a9c7cebfff 100644
--- a/spec/lib/mattermost/team_spec.rb
+++ b/spec/lib/mattermost/team_spec.rb
@@ -14,18 +14,19 @@ describe Mattermost::Team do
context 'for valid request' do
let(:response) do
[{
- "id" => "xiyro8huptfhdndadpz8r3wnbo",
- "create_at" => 1482174222155,
- "update_at" => 1482174222155,
- "delete_at" => 0,
- "display_name" => "chatops",
- "name" => "chatops",
- "email" => "admin@example.com",
- "type" => "O",
- "company_name" => "",
- "allowed_domains" => "",
- "invite_id" => "o4utakb9jtb7imctdfzbf9r5ro",
- "allow_open_invite" => false }]
+ "id" => "xiyro8huptfhdndadpz8r3wnbo",
+ "create_at" => 1482174222155,
+ "update_at" => 1482174222155,
+ "delete_at" => 0,
+ "display_name" => "chatops",
+ "name" => "chatops",
+ "email" => "admin@example.com",
+ "type" => "O",
+ "company_name" => "",
+ "allowed_domains" => "",
+ "invite_id" => "o4utakb9jtb7imctdfzbf9r5ro",
+ "allow_open_invite" => false
+ }]
end
before do
diff --git a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb
index 6a93deb5412..b6d678bac18 100644
--- a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb
+++ b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb
@@ -62,7 +62,7 @@ describe MigrateProcessCommitWorkerJobs do
end
def pop_job
- JSON.load(Sidekiq.redis { |r| r.lpop('queue:process_commit') })
+ JSON.parse(Sidekiq.redis { |r| r.lpop('queue:process_commit') })
end
before do
@@ -198,7 +198,7 @@ describe MigrateProcessCommitWorkerJobs do
let(:job) do
migration.down
- JSON.load(Sidekiq.redis { |r| r.lpop('queue:process_commit') })
+ JSON.parse(Sidekiq.redis { |r| r.lpop('queue:process_commit') })
end
it 'includes the project ID' do
diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb
index c4486a32082..4e71597521d 100644
--- a/spec/models/abuse_report_spec.rb
+++ b/spec/models/abuse_report_spec.rb
@@ -2,7 +2,7 @@ require 'rails_helper'
RSpec.describe AbuseReport, type: :model do
subject { create(:abuse_report) }
- let(:user) { create(:user) }
+ let(:user) { create(:admin) }
it { expect(subject).to be_valid }
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index b950fcdd81a..01ca1584ed2 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -29,6 +29,40 @@ describe ApplicationSetting, models: true do
it { is_expected.not_to allow_value(['test']).for(:disabled_oauth_sign_in_sources) }
end
+ describe 'default_artifacts_expire_in' do
+ it 'sets an error if it cannot parse' do
+ setting.update(default_artifacts_expire_in: 'a')
+
+ expect_invalid
+ end
+
+ it 'sets an error if it is blank' do
+ setting.update(default_artifacts_expire_in: ' ')
+
+ expect_invalid
+ end
+
+ it 'sets the value if it is valid' do
+ setting.update(default_artifacts_expire_in: '30 days')
+
+ expect(setting).to be_valid
+ expect(setting.default_artifacts_expire_in).to eq('30 days')
+ end
+
+ it 'sets the value if it is 0' do
+ setting.update(default_artifacts_expire_in: '0')
+
+ expect(setting).to be_valid
+ expect(setting.default_artifacts_expire_in).to eq('0')
+ end
+
+ def expect_invalid
+ expect(setting).to be_invalid
+ expect(setting.errors.messages)
+ .to have_key(:default_artifacts_expire_in)
+ end
+ end
+
it { is_expected.to validate_presence_of(:max_attachment_size) }
it do
@@ -62,9 +96,9 @@ describe ApplicationSetting, models: true do
describe 'inclusion' do
it { is_expected.to allow_value('custom1').for(:repository_storages) }
- it { is_expected.to allow_value(['custom2', 'custom3']).for(:repository_storages) }
+ it { is_expected.to allow_value(%w(custom2 custom3)).for(:repository_storages) }
it { is_expected.not_to allow_value('alternative').for(:repository_storages) }
- it { is_expected.not_to allow_value(['alternative', 'custom1']).for(:repository_storages) }
+ it { is_expected.not_to allow_value(%w(alternative custom1)).for(:repository_storages) }
end
describe 'presence' do
@@ -83,7 +117,7 @@ describe ApplicationSetting, models: true do
describe '#repository_storage' do
it 'returns the first storage' do
- setting.repository_storages = ['good', 'bad']
+ setting.repository_storages = %w(good bad)
expect(setting.repository_storage).to eq('good')
end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 4080092405d..b963ca4e542 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1,6 +1,7 @@
require 'spec_helper'
describe Ci::Build, :models do
+ let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:build) { create(:ci_build, pipeline: pipeline) }
let(:test_trace) { 'This is a test' }
@@ -161,11 +162,17 @@ describe Ci::Build, :models do
is_expected.to be_nil
end
- it 'when resseting value' do
+ it 'when resetting value' do
build.artifacts_expire_in = nil
is_expected.to be_nil
end
+
+ it 'when setting to 0' do
+ build.artifacts_expire_in = '0'
+
+ is_expected.to be_nil
+ end
end
describe '#commit' do
@@ -207,14 +214,16 @@ describe Ci::Build, :models do
end
it 'expects to have retried builds instead the original ones' do
- retried_rspec = Ci::Build.retry(rspec_test)
- expect(staging.depends_on_builds.map(&:id)).to contain_exactly(build.id, retried_rspec.id, rubocop_test.id)
+ project.add_developer(user)
+
+ retried_rspec = Ci::Build.retry(rspec_test, user)
+
+ expect(staging.depends_on_builds.map(&:id))
+ .to contain_exactly(build.id, retried_rspec.id, rubocop_test.id)
end
end
describe '#detailed_status' do
- let(:user) { create(:user) }
-
it 'returns a detailed status' do
expect(build.detailed_status(user))
.to be_a Gitlab::Ci::Status::Build::Cancelable
@@ -813,12 +822,16 @@ describe Ci::Build, :models do
subject { build.other_actions }
+ before do
+ project.add_developer(user)
+ end
+
it 'returns other actions' do
is_expected.to contain_exactly(other_build)
end
context 'when build is retried' do
- let!(:new_build) { Ci::Build.retry(build) }
+ let!(:new_build) { Ci::Build.retry(build, user) }
it 'does not return any of them' do
is_expected.not_to include(build, new_build)
@@ -826,7 +839,7 @@ describe Ci::Build, :models do
end
context 'when other build is retried' do
- let!(:retried_build) { Ci::Build.retry(other_build) }
+ let!(:retried_build) { Ci::Build.retry(other_build, user) }
it 'returns a retried build' do
is_expected.to contain_exactly(retried_build)
@@ -857,21 +870,29 @@ describe Ci::Build, :models do
describe '#play' do
let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
- subject { build.play }
+ before do
+ project.add_developer(user)
+ end
+
+ context 'when build is manual' do
+ it 'enqueues a build' do
+ new_build = build.play(user)
- it 'enqueues a build' do
- is_expected.to be_pending
- is_expected.to eq(build)
+ expect(new_build).to be_pending
+ expect(new_build).to eq(build)
+ end
end
- context 'for successful build' do
+ context 'when build is passed' do
before do
build.update(status: 'success')
end
it 'creates a new build' do
- is_expected.to be_pending
- is_expected.not_to eq(build)
+ new_build = build.play(user)
+
+ expect(new_build).to be_pending
+ expect(new_build).not_to eq(build)
end
end
end
@@ -1224,8 +1245,8 @@ describe Ci::Build, :models do
{ key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true },
{ key: 'CI_PROJECT_ID', value: project.id.to_s, public: true },
{ key: 'CI_PROJECT_NAME', value: project.path, public: true },
- { key: 'CI_PROJECT_PATH', value: project.path_with_namespace, public: true },
- { key: 'CI_PROJECT_NAMESPACE', value: project.namespace.path, public: true },
+ { key: 'CI_PROJECT_PATH', value: project.full_path, public: true },
+ { key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true },
{ key: 'CI_PROJECT_URL', value: project.web_url, public: true },
{ key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true }
]
@@ -1246,12 +1267,9 @@ describe Ci::Build, :models do
end
context 'when build has user' do
- let(:user) { create(:user, username: 'starter') }
let(:user_variables) do
- [
- { key: 'GITLAB_USER_ID', value: user.id.to_s, public: true },
- { key: 'GITLAB_USER_EMAIL', value: user.email, public: true }
- ]
+ [{ key: 'GITLAB_USER_ID', value: user.id.to_s, public: true },
+ { key: 'GITLAB_USER_EMAIL', value: user.email, public: true }]
end
before do
@@ -1408,7 +1426,7 @@ describe Ci::Build, :models do
end
context 'when runner is assigned to build' do
- let(:runner) { create(:ci_runner, description: 'description', tag_list: ['docker', 'linux']) }
+ let(:runner) { create(:ci_runner, description: 'description', tag_list: %w(docker linux)) }
before do
build.update(runner: runner)
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 426be74cd02..c2fc8c02bb3 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -3,8 +3,12 @@ require 'spec_helper'
describe Ci::Pipeline, models: true do
include EmailHelpers
- let(:project) { FactoryGirl.create :empty_project }
- let(:pipeline) { FactoryGirl.create :ci_empty_pipeline, status: 'created', project: project }
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project) }
+
+ let(:pipeline) do
+ create(:ci_empty_pipeline, status: :created, project: project)
+ end
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:user) }
@@ -164,9 +168,9 @@ describe Ci::Pipeline, models: true do
end
it 'returns list of stages with correct statuses' do
- expect(statuses).to eq([['build', 'failed'],
- ['test', 'success'],
- ['deploy', 'running']])
+ expect(statuses).to eq([%w(build failed),
+ %w(test success),
+ %w(deploy running)])
end
context 'when commit status is retried' do
@@ -179,9 +183,9 @@ describe Ci::Pipeline, models: true do
end
it 'ignores the previous state' do
- expect(statuses).to eq([['build', 'success'],
- ['test', 'success'],
- ['deploy', 'running']])
+ expect(statuses).to eq([%w(build success),
+ %w(test success),
+ %w(deploy running)])
end
end
end
@@ -195,7 +199,7 @@ describe Ci::Pipeline, models: true do
describe '#stages_name' do
it 'returns a valid names of stages' do
- expect(pipeline.stages_name).to eq(['build', 'test', 'deploy'])
+ expect(pipeline.stages_name).to eq(%w(build test deploy))
end
end
end
@@ -503,7 +507,9 @@ describe Ci::Pipeline, models: true do
end
describe '#status' do
- let!(:build) { create(:ci_build, :created, pipeline: pipeline, name: 'test') }
+ let(:build) do
+ create(:ci_build, :created, pipeline: pipeline, name: 'test')
+ end
subject { pipeline.reload.status }
@@ -545,13 +551,21 @@ describe Ci::Pipeline, models: true do
build.cancel
end
- it { is_expected.to eq('canceled') }
+ context 'when build is pending' do
+ let(:build) do
+ create(:ci_build, :pending, pipeline: pipeline)
+ end
+
+ it { is_expected.to eq('canceled') }
+ end
end
context 'on failure and build retry' do
before do
build.drop
- Ci::Build.retry(build)
+ project.add_developer(user)
+
+ Ci::Build.retry(build, user)
end
# We are changing a state: created > failed > running
@@ -563,8 +577,6 @@ describe Ci::Pipeline, models: true do
end
describe '#detailed_status' do
- let(:user) { create(:user) }
-
subject { pipeline.detailed_status(user) }
context 'when pipeline is created' do
@@ -720,7 +732,7 @@ describe Ci::Pipeline, models: true do
describe '#cancel_running' do
let(:latest_status) { pipeline.statuses.pluck(:status) }
- context 'when there is a running external job and created build' do
+ context 'when there is a running external job and a regular job' do
before do
create(:ci_build, :running, pipeline: pipeline)
create(:generic_commit_status, :running, pipeline: pipeline)
@@ -733,7 +745,7 @@ describe Ci::Pipeline, models: true do
end
end
- context 'when builds are in different stages' do
+ context 'when jobs are in different stages' do
before do
create(:ci_build, :running, stage_idx: 0, pipeline: pipeline)
create(:ci_build, :running, stage_idx: 1, pipeline: pipeline)
@@ -745,17 +757,34 @@ describe Ci::Pipeline, models: true do
expect(latest_status).to contain_exactly('canceled', 'canceled')
end
end
+
+ context 'when there are created builds present in the pipeline' do
+ before do
+ create(:ci_build, :running, stage_idx: 0, pipeline: pipeline)
+ create(:ci_build, :created, stage_idx: 1, pipeline: pipeline)
+
+ pipeline.cancel_running
+ end
+
+ it 'cancels created builds' do
+ expect(latest_status).to eq %w(canceled canceled)
+ end
+ end
end
describe '#retry_failed' do
let(:latest_status) { pipeline.statuses.latest.pluck(:status) }
+ before do
+ project.add_developer(user)
+ end
+
context 'when there is a failed build and failed external status' do
before do
create(:ci_build, :failed, name: 'build', pipeline: pipeline)
create(:generic_commit_status, :failed, name: 'jenkins', pipeline: pipeline)
- pipeline.retry_failed(create(:user))
+ pipeline.retry_failed(user)
end
it 'retries only build' do
@@ -768,11 +797,11 @@ describe Ci::Pipeline, models: true do
create(:ci_build, :failed, name: 'build', stage_idx: 0, pipeline: pipeline)
create(:ci_build, :failed, name: 'jenkins', stage_idx: 1, pipeline: pipeline)
- pipeline.retry_failed(create(:user))
+ pipeline.retry_failed(user)
end
it 'retries both builds' do
- expect(latest_status).to contain_exactly('pending', 'pending')
+ expect(latest_status).to contain_exactly('pending', 'created')
end
end
@@ -781,11 +810,11 @@ describe Ci::Pipeline, models: true do
create(:ci_build, :failed, name: 'build', stage_idx: 0, pipeline: pipeline)
create(:ci_build, :canceled, name: 'jenkins', stage_idx: 1, pipeline: pipeline)
- pipeline.retry_failed(create(:user))
+ pipeline.retry_failed(user)
end
it 'retries both builds' do
- expect(latest_status).to contain_exactly('pending', 'pending')
+ expect(latest_status).to contain_exactly('pending', 'created')
end
end
end
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 3f32248e52b..76ce558eea0 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -113,7 +113,7 @@ describe Ci::Runner, models: true do
context 'when runner has tags' do
before do
- runner.tag_list = ['bb', 'cc']
+ runner.tag_list = %w(bb cc)
end
shared_examples 'tagged build picker' do
@@ -169,7 +169,7 @@ describe Ci::Runner, models: true do
context 'when having runner tags' do
before do
- runner.tag_list = ['bb', 'cc']
+ runner.tag_list = %w(bb cc)
end
it 'cannot handle it for builds without matching tags' do
@@ -189,7 +189,7 @@ describe Ci::Runner, models: true do
context 'when having runner tags' do
before do
- runner.tag_list = ['bb', 'cc']
+ runner.tag_list = %w(bb cc)
build.tag_list = ['bb']
end
@@ -212,7 +212,7 @@ describe Ci::Runner, models: true do
context 'when having runner tags' do
before do
- runner.tag_list = ['bb', 'cc']
+ runner.tag_list = %w(bb cc)
build.tag_list = ['bb']
end
@@ -290,7 +290,7 @@ describe Ci::Runner, models: true do
let!(:last_update) { runner.ensure_runner_queue_value }
before do
- runner.update(description: 'new runner')
+ Ci::UpdateRunnerService.new(runner).update(description: 'new runner')
end
it 'sets a new last_update value' do
@@ -318,6 +318,25 @@ describe Ci::Runner, models: true do
end
end
+ describe '#destroy' do
+ let(:runner) { create(:ci_runner) }
+
+ context 'when there is a tick in the queue' do
+ let!(:queue_key) { runner.send(:runner_queue_key) }
+
+ before do
+ runner.tick_runner_queue
+ runner.destroy
+ end
+
+ it 'cleans up the queue' do
+ Gitlab::Redis.with do |redis|
+ expect(redis.get(queue_key)).to be_nil
+ end
+ end
+ end
+ end
+
describe '.assignable_for' do
let(:runner) { create(:ci_runner) }
let(:project) { create(:empty_project) }
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index bf4394f7d5b..36533bdd11e 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe CommitStatus, models: true do
+describe CommitStatus, :models do
let(:project) { create(:project, :repository) }
let(:pipeline) do
@@ -127,7 +127,7 @@ describe CommitStatus, models: true do
end
describe '.latest' do
- subject { CommitStatus.latest.order(:id) }
+ subject { described_class.latest.order(:id) }
let(:statuses) do
[create_status(name: 'aa', ref: 'bb', status: 'running'),
@@ -143,7 +143,7 @@ describe CommitStatus, models: true do
end
describe '.running_or_pending' do
- subject { CommitStatus.running_or_pending.order(:id) }
+ subject { described_class.running_or_pending.order(:id) }
let(:statuses) do
[create_status(name: 'aa', ref: 'bb', status: 'running'),
@@ -159,7 +159,21 @@ describe CommitStatus, models: true do
end
describe '.exclude_ignored' do
- subject { CommitStatus.exclude_ignored.order(:id) }
+ subject { described_class.after_stage(0) }
+
+ let(:statuses) do
+ [create_status(name: 'aa', stage_idx: 0),
+ create_status(name: 'cc', stage_idx: 1),
+ create_status(name: 'aa', stage_idx: 2)]
+ end
+
+ it 'returns statuses from second and third stage' do
+ is_expected.to eq(statuses.values_at(1, 2))
+ end
+ end
+
+ describe '.exclude_ignored' do
+ subject { described_class.exclude_ignored.order(:id) }
let(:statuses) do
[create_status(when: 'manual', status: 'skipped'),
diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb
index 2e3702f7520..6151d53cd91 100644
--- a/spec/models/concerns/cache_markdown_field_spec.rb
+++ b/spec/models/concerns/cache_markdown_field_spec.rb
@@ -1,7 +1,8 @@
require 'spec_helper'
describe CacheMarkdownField do
- CacheMarkdownField::CACHING_CLASSES << "ThingWithMarkdownFields"
+ caching_classes = CacheMarkdownField::CACHING_CLASSES
+ CacheMarkdownField::CACHING_CLASSES = ["ThingWithMarkdownFields"].freeze
# The minimum necessary ActiveModel to test this concern
class ThingWithMarkdownFields
@@ -54,7 +55,7 @@ describe CacheMarkdownField do
end
end
- CacheMarkdownField::CACHING_CLASSES.delete("ThingWithMarkdownFields")
+ CacheMarkdownField::CACHING_CLASSES = caching_classes
def thing_subclass(new_attr)
Class.new(ThingWithMarkdownFields) { add_attr(new_attr) }
diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb
index 30443534cca..677e60e1282 100644
--- a/spec/models/concerns/routable_spec.rb
+++ b/spec/models/concerns/routable_spec.rb
@@ -14,12 +14,14 @@ describe Group, 'Routable' do
describe 'Callbacks' do
it 'creates route record on create' do
expect(group.route.path).to eq(group.path)
+ expect(group.route.name).to eq(group.name)
end
it 'updates route record on path change' do
- group.update_attributes(path: 'wow')
+ group.update_attributes(path: 'wow', name: 'much')
expect(group.route.path).to eq('wow')
+ expect(group.route.name).to eq('much')
end
it 'ensure route path uniqueness across different objects' do
@@ -78,4 +80,34 @@ describe Group, 'Routable' do
it { is_expected.to eq([nested_group]) }
end
+
+ describe '#full_path' do
+ let(:group) { create(:group) }
+ let(:nested_group) { create(:group, parent: group) }
+
+ it { expect(group.full_path).to eq(group.path) }
+ it { expect(nested_group.full_path).to eq("#{group.full_path}/#{nested_group.path}") }
+ end
+
+ describe '#full_name' do
+ let(:group) { create(:group) }
+ let(:nested_group) { create(:group, parent: group) }
+
+ it { expect(group.full_name).to eq(group.name) }
+ it { expect(nested_group.full_name).to eq("#{group.name} / #{nested_group.name}") }
+ end
+end
+
+describe Project, 'Routable' do
+ describe '#full_path' do
+ let(:project) { build_stubbed(:empty_project) }
+
+ it { expect(project.full_path).to eq "#{project.namespace.full_path}/#{project.path}" }
+ end
+
+ describe '#full_name' do
+ let(:project) { build_stubbed(:empty_project) }
+
+ it { expect(project.full_name).to eq "#{project.namespace.human_name} / #{project.name}" }
+ end
end
diff --git a/spec/models/concerns/spammable_spec.rb b/spec/models/concerns/spammable_spec.rb
index 32935bc0b09..fd3b8307571 100644
--- a/spec/models/concerns/spammable_spec.rb
+++ b/spec/models/concerns/spammable_spec.rb
@@ -14,14 +14,16 @@ describe Issue, 'Spammable' do
end
describe 'InstanceMethods' do
+ let(:issue) { build(:issue, spam: true) }
+
it 'should be invalid if spam' do
- issue = build(:issue, spam: true)
expect(issue.valid?).to be_falsey
end
describe '#check_for_spam?' do
it 'returns true for public project' do
issue.project.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
+
expect(issue.check_for_spam?).to eq(true)
end
@@ -29,5 +31,20 @@ describe Issue, 'Spammable' do
expect(issue.check_for_spam?).to eq(false)
end
end
+
+ describe '#submittable_as_spam_by?' do
+ let(:admin) { build(:admin) }
+ let(:user) { build(:user) }
+
+ before do
+ allow(issue).to receive(:submittable_as_spam?).and_return(true)
+ end
+
+ it 'tests if the user can submit spam' do
+ expect(issue.submittable_as_spam_by?(admin)).to be(true)
+ expect(issue.submittable_as_spam_by?(user)).to be(false)
+ expect(issue.submittable_as_spam_by?(nil)).to be_nil
+ end
+ end
end
end
diff --git a/spec/models/concerns/uniquify_spec.rb b/spec/models/concerns/uniquify_spec.rb
new file mode 100644
index 00000000000..83187d732e4
--- /dev/null
+++ b/spec/models/concerns/uniquify_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe Uniquify, models: true do
+ let(:uniquify) { described_class.new }
+
+ describe "#string" do
+ it 'returns the given string if it does not exist' do
+ result = uniquify.string('test_string') { |s| false }
+
+ expect(result).to eq('test_string')
+ end
+
+ it 'returns the given string with a counter attached if the string exists' do
+ result = uniquify.string('test_string') { |s| s == 'test_string' }
+
+ expect(result).to eq('test_string1')
+ end
+
+ it 'increments the counter for each candidate string that also exists' do
+ result = uniquify.string('test_string') { |s| s == 'test_string' || s == 'test_string1' }
+
+ expect(result).to eq('test_string2')
+ end
+
+ it 'allows passing in a base function that defines the location of the counter' do
+ result = uniquify.string(-> (counter) { "test_#{counter}_string" }) do |s|
+ s == 'test__string'
+ end
+
+ expect(result).to eq('test_1_string')
+ end
+ end
+end
diff --git a/spec/models/cycle_analytics/staging_spec.rb b/spec/models/cycle_analytics/staging_spec.rb
index 9a024d533a1..78ec518ac86 100644
--- a/spec/models/cycle_analytics/staging_spec.rb
+++ b/spec/models/cycle_analytics/staging_spec.rb
@@ -18,7 +18,7 @@ describe 'CycleAnalytics#staging', feature: true do
start_time_conditions: [["merge request that closes issue is merged",
-> (context, data) do
context.merge_merge_requests_closing_issue(data[:issue])
- end ]],
+ end]],
end_time_conditions: [["merge request that closes issue is deployed to production",
-> (context, data) do
context.deploy_master
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index 960f29f3805..dce18f008f8 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -76,7 +76,8 @@ describe Environment, models: true do
end
describe '#update_merge_request_metrics?' do
- { 'production' => true,
+ {
+ 'production' => true,
'production/eu' => true,
'production/www.gitlab.com' => true,
'productioneu' => false,
@@ -155,7 +156,7 @@ describe Environment, models: true do
end
describe '#stop_with_action!' do
- let(:user) { create(:user) }
+ let(:user) { create(:admin) }
subject { environment.stop_with_action!(user) }
@@ -311,7 +312,7 @@ describe Environment, models: true do
end
describe '#generate_slug' do
- SUFFIX = "-[a-z0-9]{6}"
+ SUFFIX = "-[a-z0-9]{6}".freeze
{
"staging-12345678901234567" => "staging-123456789" + SUFFIX,
"9-staging-123456789012345" => "env-9-staging-123" + SUFFIX,
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index 16e2144d6a1..c720cc9f2c2 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -129,6 +129,14 @@ describe Member, models: true do
it { expect(described_class.request).not_to include @accepted_request_member }
end
+ describe '.non_request' do
+ it { expect(described_class.non_request).to include @master }
+ it { expect(described_class.non_request).to include @invited_member }
+ it { expect(described_class.non_request).to include @accepted_invite_member }
+ it { expect(described_class.non_request).not_to include @requested_member }
+ it { expect(described_class.non_request).to include @accepted_request_member }
+ end
+
describe '.developers' do
subject { described_class.developers.to_a }
diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb
index e4be0aba7a6..87ea2e70680 100644
--- a/spec/models/members/project_member_spec.rb
+++ b/spec/models/members/project_member_spec.rb
@@ -89,8 +89,8 @@ describe ProjectMember, models: true do
@user_1 = create :user
@user_2 = create :user
- @project_1.team << [ @user_1, :developer ]
- @project_2.team << [ @user_2, :reporter ]
+ @project_1.team << [@user_1, :developer]
+ @project_2.team << [@user_2, :reporter]
@status = @project_2.team.import(@project_1)
end
@@ -137,8 +137,8 @@ describe ProjectMember, models: true do
@user_1 = create :user
@user_2 = create :user
- @project_1.team << [ @user_1, :developer]
- @project_2.team << [ @user_2, :reporter]
+ @project_1.team << [@user_1, :developer]
+ @project_2.team << [@user_2, :reporter]
ProjectMember.truncate_teams([@project_1.id, @project_2.id])
end
diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb
index 6d599e148a2..0a10ee01506 100644
--- a/spec/models/merge_request_diff_spec.rb
+++ b/spec/models/merge_request_diff_spec.rb
@@ -109,7 +109,7 @@ describe MergeRequestDiff, models: true do
{ id: 'sha2' }
]
- expect(subject.commits_sha).to eq(['sha1', 'sha2'])
+ expect(subject.commits_sha).to eq(%w(sha1 sha2))
end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index e1e99300489..fa1b0396bcf 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -97,7 +97,7 @@ describe MergeRequest, models: true do
commit = double('commit1', safe_message: "Fixes #{issue.to_reference}")
allow(subject).to receive(:commits).and_return([commit])
- expect { subject.cache_merge_request_closes_issues! }.to change(subject.merge_requests_closing_issues, :count).by(1)
+ expect { subject.cache_merge_request_closes_issues!(subject.author) }.to change(subject.merge_requests_closing_issues, :count).by(1)
end
it 'does not cache issues from external trackers' do
@@ -106,7 +106,7 @@ describe MergeRequest, models: true do
commit = double('commit1', safe_message: "Fixes #{issue.to_reference}")
allow(subject).to receive(:commits).and_return([commit])
- expect { subject.cache_merge_request_closes_issues! }.not_to change(subject.merge_requests_closing_issues, :count)
+ expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to change(subject.merge_requests_closing_issues, :count)
end
end
@@ -209,6 +209,50 @@ describe MergeRequest, models: true do
end
end
+ describe '#diff_size' do
+ let(:merge_request) do
+ build(:merge_request, source_branch: 'expand-collapse-files', target_branch: 'master')
+ end
+
+ context 'when there are MR diffs' do
+ before do
+ merge_request.save
+ end
+
+ it 'returns the correct count' do
+ expect(merge_request.diff_size).to eq(105)
+ end
+
+ it 'does not perform highlighting' do
+ expect(Gitlab::Diff::Highlight).not_to receive(:new)
+
+ merge_request.diff_size
+ end
+ end
+
+ context 'when there are no MR diffs' do
+ before do
+ merge_request.compare = CompareService.new(
+ merge_request.source_project,
+ merge_request.source_branch
+ ).execute(
+ merge_request.target_project,
+ merge_request.target_branch
+ )
+ end
+
+ it 'returns the correct count' do
+ expect(merge_request.diff_size).to eq(105)
+ end
+
+ it 'does not perform highlighting' do
+ expect(Gitlab::Diff::Highlight).not_to receive(:new)
+
+ merge_request.diff_size
+ end
+ end
+ end
+
describe "#related_notes" do
let!(:merge_request) { create(:merge_request) }
@@ -300,7 +344,7 @@ describe MergeRequest, models: true do
allow(subject.project).to receive(:default_branch).
and_return(subject.target_branch)
- expect(subject.issues_mentioned_but_not_closing).to match_array([mentioned_issue])
+ expect(subject.issues_mentioned_but_not_closing(subject.author)).to match_array([mentioned_issue])
end
end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 7bb1657bc3a..3f9c4289de9 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -3,21 +3,32 @@ require 'spec_helper'
describe Namespace, models: true do
let!(:namespace) { create(:namespace) }
- it { is_expected.to have_many :projects }
- it { is_expected.to have_many :project_statistics }
- it { is_expected.to belong_to :parent }
- it { is_expected.to have_many :children }
+ describe 'associations' do
+ it { is_expected.to have_many :projects }
+ it { is_expected.to have_many :project_statistics }
+ it { is_expected.to belong_to :parent }
+ it { is_expected.to have_many :children }
+ end
- it { is_expected.to validate_presence_of(:name) }
- it { is_expected.to validate_uniqueness_of(:name).scoped_to(:parent_id) }
- it { is_expected.to validate_length_of(:name).is_at_most(255) }
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:name) }
+ it { is_expected.to validate_uniqueness_of(:name).scoped_to(:parent_id) }
+ it { is_expected.to validate_length_of(:name).is_at_most(255) }
+ it { is_expected.to validate_length_of(:description).is_at_most(255) }
+ it { is_expected.to validate_presence_of(:path) }
+ it { is_expected.to validate_length_of(:path).is_at_most(255) }
+ it { is_expected.to validate_presence_of(:owner) }
- it { is_expected.to validate_length_of(:description).is_at_most(255) }
+ it 'does not allow too deep nesting' do
+ ancestors = (1..21).to_a
+ nested = build(:namespace, parent: namespace)
- it { is_expected.to validate_presence_of(:path) }
- it { is_expected.to validate_length_of(:path).is_at_most(255) }
+ allow(nested).to receive(:ancestors).and_return(ancestors)
- it { is_expected.to validate_presence_of(:owner) }
+ expect(nested).not_to be_valid
+ expect(nested.errors[:parent_id].first).to eq('has too deep level of nesting')
+ end
+ end
describe "Respond to" do
it { is_expected.to respond_to(:human_name) }
@@ -25,7 +36,7 @@ describe Namespace, models: true do
end
describe '#to_param' do
- it { expect(namespace.to_param).to eq(namespace.path) }
+ it { expect(namespace.to_param).to eq(namespace.full_path) }
end
describe '#human_name' do
@@ -140,7 +151,7 @@ describe Namespace, models: true do
describe :rm_dir do
let!(:project) { create(:empty_project, namespace: namespace) }
- let!(:path) { File.join(Gitlab.config.repositories.storages.default, namespace.path) }
+ let!(:path) { File.join(Gitlab.config.repositories.storages.default, namespace.full_path) }
it "removes its dirs when deleted" do
namespace.destroy
@@ -175,22 +186,6 @@ describe Namespace, models: true do
end
end
- describe '#full_path' do
- let(:group) { create(:group) }
- let(:nested_group) { create(:group, parent: group) }
-
- it { expect(group.full_path).to eq(group.path) }
- it { expect(nested_group.full_path).to eq("#{group.path}/#{nested_group.path}") }
- end
-
- describe '#full_name' do
- let(:group) { create(:group) }
- let(:nested_group) { create(:group, parent: group) }
-
- it { expect(group.full_name).to eq(group.name) }
- it { expect(nested_group.full_name).to eq("#{group.name} / #{nested_group.name}") }
- end
-
describe '#ancestors' do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb
index 497a626a418..4014d6129ee 100644
--- a/spec/models/project_services/bamboo_service_spec.rb
+++ b/spec/models/project_services/bamboo_service_spec.rb
@@ -181,7 +181,7 @@ describe BambooService, models: true, caching: true do
end
it 'sets commit status to "pending" when response has no results' do
- stub_request(body: %Q({"results":{"results":{"size":"0"}}}))
+ stub_request(body: %q({"results":{"results":{"size":"0"}}}))
is_expected.to eq('pending')
end
diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb
index dbd23ff5491..05b602d8106 100644
--- a/spec/models/project_services/buildkite_service_spec.rb
+++ b/spec/models/project_services/buildkite_service_spec.rb
@@ -92,7 +92,7 @@ describe BuildkiteService, models: true, caching: true do
end
it 'passes through build status untouched when status is 200' do
- stub_request(body: %Q({"status":"Great Success"}))
+ stub_request(body: %q({"status":"Great Success"}))
is_expected.to eq('Great Success')
end
@@ -101,7 +101,7 @@ describe BuildkiteService, models: true, caching: true do
end
def stub_request(status: 200, body: nil)
- body ||= %Q({"status":"success"})
+ body ||= %q({"status":"success"})
buildkite_full_url = 'https://gitlab.buildkite.com/status/secret-sauce-status-token.json?commit=123'
WebMock.stub_request(:get, buildkite_full_url).to_return(
diff --git a/spec/models/project_services/chat_message/build_message_spec.rb b/spec/models/project_services/chat_message/build_message_spec.rb
index 50ad5013df9..3bd7ec18ae0 100644
--- a/spec/models/project_services/chat_message/build_message_spec.rb
+++ b/spec/models/project_services/chat_message/build_message_spec.rb
@@ -11,21 +11,28 @@ describe ChatMessage::BuildMessage do
project_name: 'project_name',
project_url: 'http://example.gitlab.com',
+ build_id: 1,
+ build_name: build_name,
+ build_stage: stage,
commit: {
status: status,
author_name: 'hacker',
+ author_url: 'http://example.gitlab.com/hacker',
duration: duration,
},
}
end
let(:message) { build_message }
+ let(:stage) { 'test' }
+ let(:status) { 'success' }
+ let(:build_name) { 'rspec' }
+ let(:duration) { 10 }
context 'build succeeded' do
let(:status) { 'success' }
let(:color) { 'good' }
- let(:duration) { 10 }
let(:message) { build_message('passed') }
it 'returns a message with information about succeeded build' do
@@ -38,7 +45,6 @@ describe ChatMessage::BuildMessage do
context 'build failed' do
let(:status) { 'failed' }
let(:color) { 'danger' }
- let(:duration) { 10 }
it 'returns a message with information about failed build' do
expect(subject.pretext).to be_empty
@@ -47,11 +53,25 @@ describe ChatMessage::BuildMessage do
end
end
- def build_message(status_text = status)
+ it 'returns a message with information on build' do
+ expect(subject.fallback).to include("on build <http://example.gitlab.com/builds/1|#{build_name}>")
+ end
+
+ it 'returns a message with stage name' do
+ expect(subject.fallback).to include("of stage #{stage}")
+ end
+
+ it 'returns a message with link to author' do
+ expect(subject.fallback).to include("by <http://example.gitlab.com/hacker|hacker>")
+ end
+
+ def build_message(status_text = status, stage_text = stage, build_text = build_name)
"<http://example.gitlab.com|project_name>:" \
" Commit <http://example.gitlab.com/commit/" \
"97de212e80737a608d939f648d959671fb0a0142/builds|97de212e>" \
" of <http://example.gitlab.com/commits/develop|develop> branch" \
- " by hacker #{status_text} in #{duration} #{'second'.pluralize(duration)}"
+ " by <http://example.gitlab.com/hacker|hacker> #{status_text}" \
+ " on build <http://example.gitlab.com/builds/1|#{build_text}>" \
+ " of stage #{stage_text} in #{duration} #{'second'.pluralize(duration)}"
end
end
diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb
index f9307d6de7b..044737c6026 100644
--- a/spec/models/project_services/drone_ci_service_spec.rb
+++ b/spec/models/project_services/drone_ci_service_spec.rb
@@ -28,7 +28,7 @@ describe DroneCiService, models: true, caching: true do
shared_context :drone_ci_service do
let(:drone) { DroneCiService.new }
let(:project) { create(:project, :repository, name: 'project') }
- let(:path) { "#{project.namespace.path}/#{project.path}" }
+ let(:path) { project.full_path }
let(:drone_url) { 'http://drone.example.com' }
let(:sha) { '2ab7834c' }
let(:branch) { 'dev' }
@@ -50,7 +50,7 @@ describe DroneCiService, models: true, caching: true do
end
def stub_request(status: 200, body: nil)
- body ||= %Q({"status":"success"})
+ body ||= %q({"status":"success"})
WebMock.stub_request(:get, commit_status_path).to_return(
status: status,
@@ -95,12 +95,12 @@ describe DroneCiService, models: true, caching: true do
is_expected.to eq(:error)
end
- { "killed" => :canceled,
+ {
+ "killed" => :canceled,
"failure" => :failed,
"error" => :failed,
- "success" => "success",
+ "success" => "success"
}.each do |drone_status, our_status|
-
it "sets commit status to #{our_status.inspect} when returned status is #{drone_status.inspect}" do
stub_request(body: %Q({"status":"#{drone_status}"}))
diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb
index b9fb6f3f6f4..d5a16226d9d 100644
--- a/spec/models/project_services/irker_service_spec.rb
+++ b/spec/models/project_services/irker_service_spec.rb
@@ -59,8 +59,8 @@ describe IrkerService, models: true do
conn = @irker_server.accept
conn.readlines.each do |line|
- msg = JSON.load(line.chomp("\n"))
- expect(msg.keys).to match_array(['to', 'privmsg'])
+ msg = JSON.parse(line.chomp("\n"))
+ expect(msg.keys).to match_array(%w(to privmsg))
expect(msg['to']).to match_array(["irc://chat.freenode.net/#commits",
"irc://test.net/#test"])
end
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 2f6b159d76e..4bca0229e7a 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -135,7 +135,7 @@ describe JiraService, models: true do
url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/#{merge_request.diff_head_sha}",
title: "GitLab: Solved by commit #{merge_request.diff_head_sha}.",
icon: { title: "GitLab", url16x16: "https://gitlab.com/favicon.ico" },
- status: { resolved: true, icon: { url16x16: "http://www.openwebgraphics.com/resources/data/1768/16x16_apply.png", title: "Closed" } }
+ status: { resolved: true }
}
)
).once
diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb
index 9052479d35e..24356447fed 100644
--- a/spec/models/project_services/kubernetes_service_spec.rb
+++ b/spec/models/project_services/kubernetes_service_spec.rb
@@ -171,7 +171,7 @@ describe KubernetesService, models: true, caching: true do
context 'with invalid pods' do
it 'returns no terminals' do
- stub_reactive_cache(service, pods: [ { "bad" => "pod" } ])
+ stub_reactive_cache(service, pods: [{ "bad" => "pod" }])
is_expected.to be_empty
end
@@ -184,7 +184,7 @@ describe KubernetesService, models: true, caching: true do
before do
stub_reactive_cache(
service,
- pods: [ pod, pod, kube_pod(app: "should-be-filtered-out") ]
+ pods: [pod, pod, kube_pod(app: "should-be-filtered-out")]
)
end
diff --git a/spec/models/project_services/mattermost_slash_commands_service_spec.rb b/spec/models/project_services/mattermost_slash_commands_service_spec.rb
index 98f3d420c8a..598ff232c82 100644
--- a/spec/models/project_services/mattermost_slash_commands_service_spec.rb
+++ b/spec/models/project_services/mattermost_slash_commands_service_spec.rb
@@ -36,7 +36,8 @@ describe MattermostSlashCommandsService, :models do
description: "Perform common operations on: #{project.name_with_namespace}",
display_name: "GitLab / #{project.name_with_namespace}",
method: 'P',
- username: 'GitLab' }.to_json).
+ username: 'GitLab'
+ }.to_json).
to_return(
status: 200,
headers: { 'Content-Type' => 'application/json' },
diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb
index a1edd083aa1..77b18e1c7d0 100644
--- a/spec/models/project_services/teamcity_service_spec.rb
+++ b/spec/models/project_services/teamcity_service_spec.rb
@@ -143,7 +143,7 @@ describe TeamcityService, models: true, caching: true do
end
it 'returns a build URL when teamcity_url has no trailing slash' do
- stub_request(body: %Q({"build":{"id":"666"}}))
+ stub_request(body: %q({"build":{"id":"666"}}))
is_expected.to eq('http://gitlab.com/teamcity/viewLog.html?buildId=666&buildTypeId=foo')
end
@@ -152,7 +152,7 @@ describe TeamcityService, models: true, caching: true do
let(:teamcity_url) { 'http://gitlab.com/teamcity/' }
it 'returns a build URL' do
- stub_request(body: %Q({"build":{"id":"666"}}))
+ stub_request(body: %q({"build":{"id":"666"}}))
is_expected.to eq('http://gitlab.com/teamcity/viewLog.html?buildId=666&buildTypeId=foo')
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 2129bcbd74d..8a27dfff203 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -275,13 +275,6 @@ describe Project, models: true do
it { is_expected.to delegate_method(:add_master).to(:team) }
end
- describe '#name_with_namespace' do
- let(:project) { build_stubbed(:empty_project) }
-
- it { expect(project.name_with_namespace).to eq "#{project.namespace.human_name} / #{project.name}" }
- it { expect(project.human_name).to eq project.name_with_namespace }
- end
-
describe '#to_reference' do
let(:owner) { create(:user, name: 'Gitlab') }
let(:namespace) { create(:namespace, path: 'sample-namespace', owner: owner) }
@@ -409,7 +402,7 @@ describe Project, models: true do
let(:project) { create(:empty_project, path: "somewhere") }
it 'returns the full web URL for this repo' do
- expect(project.web_url).to eq("#{Gitlab.config.gitlab.url}/#{project.namespace.path}/somewhere")
+ expect(project.web_url).to eq("#{Gitlab.config.gitlab.url}/#{project.namespace.full_path}/somewhere")
end
end
@@ -810,7 +803,7 @@ describe Project, models: true do
end
let(:avatar_path) do
- "/#{project.namespace.name}/#{project.path}/avatar"
+ "/#{project.full_path}/avatar"
end
it { should eq "http://#{Gitlab.config.gitlab.host}#{avatar_path}" }
@@ -1155,16 +1148,14 @@ describe Project, models: true do
end
it 'renames a repository' do
- ns = project.namespace_dir
-
expect(gitlab_shell).to receive(:mv_repository).
ordered.
- with(project.repository_storage_path, "#{ns}/foo", "#{ns}/#{project.path}").
+ with(project.repository_storage_path, "#{project.namespace.full_path}/foo", "#{project.full_path}").
and_return(true)
expect(gitlab_shell).to receive(:mv_repository).
ordered.
- with(project.repository_storage_path, "#{ns}/foo.wiki", "#{ns}/#{project.path}.wiki").
+ with(project.repository_storage_path, "#{project.namespace.full_path}/foo.wiki", "#{project.full_path}.wiki").
and_return(true)
expect_any_instance_of(SystemHooksService).
@@ -1173,7 +1164,7 @@ describe Project, models: true do
expect_any_instance_of(Gitlab::UploadsTransfer).
to receive(:rename_project).
- with('foo', project.path, ns)
+ with('foo', project.path, project.namespace.full_path)
expect(project).to receive(:expire_caches_before_rename)
@@ -1545,7 +1536,7 @@ describe Project, models: true do
it 'schedules a RepositoryForkWorker job' do
expect(RepositoryForkWorker).to receive(:perform_async).
with(project.id, forked_from_project.repository_storage_path,
- forked_from_project.path_with_namespace, project.namespace.path)
+ forked_from_project.path_with_namespace, project.namespace.full_path)
project.add_import_job
end
@@ -1759,7 +1750,7 @@ describe Project, models: true do
describe 'inside_path' do
let!(:project1) { create(:empty_project) }
let!(:project2) { create(:empty_project) }
- let!(:path) { project1.namespace.path }
+ let!(:path) { project1.namespace.full_path }
it { expect(Project.inside_path(path)).to eq([project1]) }
end
@@ -1840,13 +1831,27 @@ describe Project, models: true do
end
end
+ describe '#parent' do
+ let(:project) { create(:empty_project) }
+
+ it { expect(project.parent).to eq(project.namespace) }
+ end
+
+ describe '#parent_changed?' do
+ let(:project) { create(:empty_project) }
+
+ before { project.namespace_id = 7 }
+
+ it { expect(project.parent_changed?).to be_truthy }
+ end
+
def enable_lfs
allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
end
describe '#pages_url' do
- let(:group) { create :group, name: group_name }
- let(:project) { create :empty_project, namespace: group, name: project_name }
+ let(:group) { create :group, name: 'Group' }
+ let(:nested_group) { create :group, parent: group }
let(:domain) { 'Example.com' }
subject { project.pages_url }
@@ -1856,18 +1861,37 @@ describe Project, models: true do
allow(Gitlab.config.pages).to receive(:url).and_return('http://example.com')
end
- context 'group page' do
- let(:group_name) { 'Group' }
- let(:project_name) { 'group.example.com' }
+ context 'top-level group' do
+ let(:project) { create :empty_project, namespace: group, name: project_name }
+
+ context 'group page' do
+ let(:project_name) { 'group.example.com' }
+
+ it { is_expected.to eq("http://group.example.com") }
+ end
+
+ context 'project page' do
+ let(:project_name) { 'Project' }
- it { is_expected.to eq("http://group.example.com") }
+ it { is_expected.to eq("http://group.example.com/project") }
+ end
end
- context 'project page' do
- let(:group_name) { 'Group' }
- let(:project_name) { 'Project' }
+ context 'nested group' do
+ let(:project) { create :empty_project, namespace: nested_group, name: project_name }
+ let(:expected_url) { "http://group.example.com/#{nested_group.path}/#{project.path}" }
- it { is_expected.to eq("http://group.example.com/project") }
+ context 'group page' do
+ let(:project_name) { 'group.example.com' }
+
+ it { is_expected.to eq(expected_url) }
+ end
+
+ context 'project page' do
+ let(:project_name) { 'Project' }
+
+ it { is_expected.to eq(expected_url) }
+ end
end
end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 9bfa6409607..43aac31d8b7 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -351,6 +351,17 @@ describe Repository, models: true do
expect(blob.data).to eq('Changelog!')
end
+ it 'respects the autocrlf setting' do
+ repository.commit_file(user, 'hello.txt', "Hello,\r\nWorld",
+ message: 'Add hello world',
+ branch_name: 'master',
+ update: true)
+
+ blob = repository.blob_at('master', 'hello.txt')
+
+ expect(blob.data).to eq("Hello,\nWorld")
+ end
+
context "when an author is specified" do
it "uses the given email/name to set the commit's author" do
expect do
@@ -1371,13 +1382,13 @@ describe Repository, models: true do
describe '#branch_count' do
it 'returns the number of branches' do
- expect(repository.branch_count).to be_an_instance_of(Fixnum)
+ expect(repository.branch_count).to be_an(Integer)
end
end
describe '#tag_count' do
it 'returns the number of tags' do
- expect(repository.tag_count).to be_an_instance_of(Fixnum)
+ expect(repository.tag_count).to be_an(Integer)
end
end
@@ -1727,7 +1738,7 @@ describe Repository, models: true do
context 'with an existing repository' do
it 'returns the commit count' do
- expect(repository.commit_count).to be_an_instance_of(Fixnum)
+ expect(repository.commit_count).to be_an(Integer)
end
end
end
diff --git a/spec/models/route_spec.rb b/spec/models/route_spec.rb
index dd2a5109abc..0b222022e62 100644
--- a/spec/models/route_spec.rb
+++ b/spec/models/route_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Route, models: true do
- let!(:group) { create(:group, path: 'gitlab') }
+ let!(:group) { create(:group, path: 'gitlab', name: 'gitlab') }
let!(:route) { group.route }
describe 'relationships' do
@@ -15,17 +15,42 @@ describe Route, models: true do
end
describe '#rename_descendants' do
- let!(:nested_group) { create(:group, path: "test", parent: group) }
- let!(:deep_nested_group) { create(:group, path: "foo", parent: nested_group) }
- let!(:similar_group) { create(:group, path: 'gitlab-org') }
+ let!(:nested_group) { create(:group, path: 'test', name: 'test', parent: group) }
+ let!(:deep_nested_group) { create(:group, path: 'foo', name: 'foo', parent: nested_group) }
+ let!(:similar_group) { create(:group, path: 'gitlab-org', name: 'gitlab-org') }
- before { route.update_attributes(path: 'bar') }
+ context 'path update' do
+ context 'when route name is set' do
+ before { route.update_attributes(path: 'bar') }
- it "updates children routes with new path" do
- expect(described_class.exists?(path: 'bar')).to be_truthy
- expect(described_class.exists?(path: 'bar/test')).to be_truthy
- expect(described_class.exists?(path: 'bar/test/foo')).to be_truthy
- expect(described_class.exists?(path: 'gitlab-org')).to be_truthy
+ it "updates children routes with new path" do
+ expect(described_class.exists?(path: 'bar')).to be_truthy
+ expect(described_class.exists?(path: 'bar/test')).to be_truthy
+ expect(described_class.exists?(path: 'bar/test/foo')).to be_truthy
+ expect(described_class.exists?(path: 'gitlab-org')).to be_truthy
+ end
+ end
+
+ context 'when route name is nil' do
+ before do
+ route.update_column(:name, nil)
+ end
+
+ it "does not fail" do
+ expect(route.update_attributes(path: 'bar')).to be_truthy
+ end
+ end
+ end
+
+ context 'name update' do
+ before { route.update_attributes(name: 'bar') }
+
+ it "updates children routes with new path" do
+ expect(described_class.exists?(name: 'bar')).to be_truthy
+ expect(described_class.exists?(name: 'bar / test')).to be_truthy
+ expect(described_class.exists?(name: 'bar / test / foo')).to be_truthy
+ expect(described_class.exists?(name: 'gitlab-org')).to be_truthy
+ end
end
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index fe88ec63af6..6356f8b6c92 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -19,9 +19,10 @@ describe User, models: true do
it { is_expected.to have_many(:project_members).dependent(:destroy) }
it { is_expected.to have_many(:groups) }
it { is_expected.to have_many(:keys).dependent(:destroy) }
+ it { is_expected.to have_many(:deploy_keys).dependent(:destroy) }
it { is_expected.to have_many(:events).dependent(:destroy) }
it { is_expected.to have_many(:recent_events).class_name('Event') }
- it { is_expected.to have_many(:issues).dependent(:destroy) }
+ it { is_expected.to have_many(:issues).dependent(:restrict_with_exception) }
it { is_expected.to have_many(:notes).dependent(:destroy) }
it { is_expected.to have_many(:assigned_issues).dependent(:nullify) }
it { is_expected.to have_many(:merge_requests).dependent(:destroy) }
@@ -141,6 +142,11 @@ describe User, models: true do
user = build(:user, email: "example@test.com")
expect(user).to be_invalid
end
+
+ it 'accepts example@test.com when added by another user' do
+ user = build(:user, email: "example@test.com", created_by_id: 1)
+ expect(user).to be_valid
+ end
end
context 'domain blacklist' do
@@ -159,6 +165,11 @@ describe User, models: true do
user = build(:user, email: 'info@example.com')
expect(user).not_to be_valid
end
+
+ it 'accepts info@example.com when added by another user' do
+ user = build(:user, email: 'info@example.com', created_by_id: 1)
+ expect(user).to be_valid
+ end
end
context 'when a signup domain is blacklisted but a wildcard subdomain is allowed' do
@@ -197,6 +208,22 @@ describe User, models: true do
end
end
end
+
+ describe 'ghost users' do
+ it 'does not allow a non-blocked ghost user' do
+ user = build(:user, :ghost)
+ user.state = 'active'
+
+ expect(user).to be_invalid
+ end
+
+ it 'allows a blocked ghost user' do
+ user = build(:user, :ghost)
+ user.state = 'blocked'
+
+ expect(user).to be_valid
+ end
+ end
end
describe "scopes" do
@@ -293,6 +320,34 @@ describe User, models: true do
end
end
+ shared_context 'user keys' do
+ let(:user) { create(:user) }
+ let!(:key) { create(:key, user: user) }
+ let!(:deploy_key) { create(:deploy_key, user: user) }
+ end
+
+ describe '#keys' do
+ include_context 'user keys'
+
+ context 'with key and deploy key stored' do
+ it 'returns stored key, but not deploy_key' do
+ expect(user.keys).to include key
+ expect(user.keys).not_to include deploy_key
+ end
+ end
+ end
+
+ describe '#deploy_keys' do
+ include_context 'user keys'
+
+ context 'with key and deploy key stored' do
+ it 'returns stored deploy key, but not normal key' do
+ expect(user.deploy_keys).to include deploy_key
+ expect(user.deploy_keys).not_to include key
+ end
+ end
+ end
+
describe '#confirm' do
before do
allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(true)
@@ -543,18 +598,16 @@ describe User, models: true do
it "applies defaults to user" do
expect(user.projects_limit).to eq(Gitlab.config.gitlab.default_projects_limit)
expect(user.can_create_group).to eq(Gitlab.config.gitlab.default_can_create_group)
- expect(user.theme_id).to eq(Gitlab.config.gitlab.default_theme)
expect(user.external).to be_falsey
end
end
describe 'with default overrides' do
- let(:user) { User.new(projects_limit: 123, can_create_group: false, can_create_team: true, theme_id: 1) }
+ let(:user) { User.new(projects_limit: 123, can_create_group: false, can_create_team: true) }
it "applies defaults to user" do
expect(user.projects_limit).to eq(123)
expect(user.can_create_group).to be_falsey
- expect(user.theme_id).to eq(1)
end
end
@@ -656,9 +709,7 @@ describe User, models: true do
end
describe '.search_with_secondary_emails' do
- def search_with_secondary_emails(query)
- described_class.search_with_secondary_emails(query)
- end
+ delegate :search_with_secondary_emails, to: :described_class
let!(:user) { create(:user) }
let!(:email) { create(:email) }
@@ -1455,4 +1506,41 @@ describe User, models: true do
expect(user.admin).to be true
end
end
+
+ describe '.ghost' do
+ it "creates a ghost user if one isn't already present" do
+ ghost = User.ghost
+
+ expect(ghost).to be_ghost
+ expect(ghost).to be_persisted
+ end
+
+ it "does not create a second ghost user if one is already present" do
+ expect do
+ User.ghost
+ User.ghost
+ end.to change { User.count }.by(1)
+ expect(User.ghost).to eq(User.ghost)
+ end
+
+ context "when a regular user exists with the username 'ghost'" do
+ it "creates a ghost user with a non-conflicting username" do
+ create(:user, username: 'ghost')
+ ghost = User.ghost
+
+ expect(ghost).to be_persisted
+ expect(ghost.username).to eq('ghost1')
+ end
+ end
+
+ context "when a regular user exists with the email 'ghost@example.com'" do
+ it "creates a ghost user with a non-conflicting email" do
+ create(:user, email: 'ghost@example.com')
+ ghost = User.ghost
+
+ expect(ghost).to be_persisted
+ expect(ghost.email).to eq('ghost1@example.com')
+ end
+ end
+ end
end
diff --git a/spec/models/wiki_directory_spec.rb b/spec/models/wiki_directory_spec.rb
new file mode 100644
index 00000000000..1caaa557085
--- /dev/null
+++ b/spec/models/wiki_directory_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+RSpec.describe WikiDirectory, models: true do
+ describe 'validations' do
+ subject { build(:wiki_directory) }
+
+ it { is_expected.to validate_presence_of(:slug) }
+ end
+
+ describe '#initialize' do
+ context 'when there are pages' do
+ let(:pages) { [build(:wiki_page)] }
+ let(:directory) { WikiDirectory.new('/path_up_to/dir', pages) }
+
+ it 'sets the slug attribute' do
+ expect(directory.slug).to eq('/path_up_to/dir')
+ end
+
+ it 'sets the pages attribute' do
+ expect(directory.pages).to eq(pages)
+ end
+ end
+
+ context 'when there are no pages' do
+ let(:directory) { WikiDirectory.new('/path_up_to/dir') }
+
+ it 'sets the slug attribute' do
+ expect(directory.slug).to eq('/path_up_to/dir')
+ end
+
+ it 'sets the pages attribute to an empty array' do
+ expect(directory.pages).to eq([])
+ end
+ end
+ end
+
+ describe '#to_partial_path' do
+ it 'returns the relative path to the partial to be used' do
+ directory = build(:wiki_directory)
+
+ expect(directory.to_partial_path).to eq('projects/wikis/wiki_directory')
+ end
+ end
+end
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index 5c34b1b0a30..753dc938c52 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -7,6 +7,75 @@ describe WikiPage, models: true do
subject { WikiPage.new(wiki) }
+ describe '.group_by_directory' do
+ context 'when there are no pages' do
+ it 'returns an empty array' do
+ expect(WikiPage.group_by_directory(nil)).to eq([])
+ expect(WikiPage.group_by_directory([])).to eq([])
+ end
+ end
+
+ context 'when there are pages' do
+ before do
+ create_page('dir_1/dir_1_1/page_3', 'content')
+ create_page('dir_1/page_2', 'content')
+ create_page('dir_2/page_5', 'content')
+ create_page('dir_2/page_4', 'content')
+ create_page('page_1', 'content')
+ end
+ let(:page_1) { wiki.find_page('page_1') }
+ let(:dir_1) do
+ WikiDirectory.new('dir_1', [wiki.find_page('dir_1/page_2')])
+ end
+ let(:dir_1_1) do
+ WikiDirectory.new('dir_1/dir_1_1', [wiki.find_page('dir_1/dir_1_1/page_3')])
+ end
+ let(:dir_2) do
+ pages = [wiki.find_page('dir_2/page_5'),
+ wiki.find_page('dir_2/page_4')]
+ WikiDirectory.new('dir_2', pages)
+ end
+
+ it 'returns an array with pages and directories' do
+ expected_grouped_entries = [page_1, dir_1, dir_1_1, dir_2]
+
+ grouped_entries = WikiPage.group_by_directory(wiki.pages)
+
+ grouped_entries.each_with_index do |page_or_dir, i|
+ expected_page_or_dir = expected_grouped_entries[i]
+ expected_slugs = get_slugs(expected_page_or_dir)
+ slugs = get_slugs(page_or_dir)
+
+ expect(slugs).to match_array(expected_slugs)
+ end
+ end
+
+ it 'returns an array sorted by alphabetical position' do
+ # Directories and pages within directories are sorted alphabetically.
+ # Pages at root come before everything.
+ expected_order = ['page_1', 'dir_1/page_2', 'dir_1/dir_1_1/page_3',
+ 'dir_2/page_4', 'dir_2/page_5']
+
+ grouped_entries = WikiPage.group_by_directory(wiki.pages)
+
+ actual_order =
+ grouped_entries.map do |page_or_dir|
+ get_slugs(page_or_dir)
+ end.
+ flatten
+ expect(actual_order).to eq(expected_order)
+ end
+ end
+ end
+
+ describe '.unhyphenize' do
+ it 'removes hyphens from a name' do
+ name = 'a-name--with-hyphens'
+
+ expect(WikiPage.unhyphenize(name)).to eq('a name with hyphens')
+ end
+ end
+
describe "#initialize" do
context "when initialized with an existing gollum page" do
before do
@@ -189,6 +258,26 @@ describe WikiPage, models: true do
end
end
+ describe '#directory' do
+ context 'when the page is at the root directory' do
+ it 'returns an empty string' do
+ create_page('file', 'content')
+ page = wiki.find_page('file')
+
+ expect(page.directory).to eq('')
+ end
+ end
+
+ context 'when the page is inside an actual directory' do
+ it 'returns the full directory hierarchy' do
+ create_page('dir_1/dir_1_1/file', 'content')
+ page = wiki.find_page('dir_1/dir_1_1/file')
+
+ expect(page.directory).to eq('dir_1/dir_1_1')
+ end
+ end
+ end
+
describe '#historical?' do
before do
create_page('Update', 'content')
@@ -221,6 +310,27 @@ describe WikiPage, models: true do
end
end
+ describe '#to_partial_path' do
+ it 'returns the relative path to the partial to be used' do
+ page = build(:wiki_page)
+
+ expect(page.to_partial_path).to eq('projects/wikis/wiki_page')
+ end
+ end
+
+ describe '#==' do
+ let(:original_wiki_page) { create(:wiki_page) }
+
+ it 'returns true for identical wiki page' do
+ expect(original_wiki_page).to eq(original_wiki_page)
+ end
+
+ it 'returns false for updated wiki page' do
+ updated_wiki_page = original_wiki_page.update("Updated content")
+ expect(original_wiki_page).not_to eq(updated_wiki_page)
+ end
+ end
+
private
def remove_temp_repo(path)
@@ -239,4 +349,12 @@ describe WikiPage, models: true do
page = wiki.wiki.paged(title)
wiki.wiki.delete_page(page, commit_details)
end
+
+ def get_slugs(page_or_dir)
+ if page_or_dir.is_a? WikiPage
+ [page_or_dir.slug]
+ else
+ page_or_dir.pages.present? ? page_or_dir.pages.map(&:slug) : []
+ end
+ end
end
diff --git a/spec/policies/user_policy_spec.rb b/spec/policies/user_policy_spec.rb
new file mode 100644
index 00000000000..d5761390d39
--- /dev/null
+++ b/spec/policies/user_policy_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe UserPolicy, models: true do
+ let(:current_user) { create(:user) }
+ let(:user) { create(:user) }
+
+ subject { described_class.abilities(current_user, user).to_set }
+
+ describe "reading a user's information" do
+ it { is_expected.to include(:read_user) }
+ end
+
+ describe "destroying a user" do
+ context "when a regular user tries to destroy another regular user" do
+ it { is_expected.not_to include(:destroy_user) }
+ end
+
+ context "when a regular user tries to destroy themselves" do
+ let(:current_user) { user }
+
+ it { is_expected.to include(:destroy_user) }
+ end
+
+ context "when an admin user tries to destroy a regular user" do
+ let(:current_user) { create(:user, :admin) }
+
+ it { is_expected.to include(:destroy_user) }
+ end
+
+ context "when an admin user tries to destroy a ghost user" do
+ let(:current_user) { create(:user, :admin) }
+ let(:user) { create(:user, :ghost) }
+
+ it { is_expected.not_to include(:destroy_user) }
+ end
+ end
+end
diff --git a/spec/requests/api/access_requests_spec.rb b/spec/requests/api/access_requests_spec.rb
index e487297748b..919c98d6437 100644
--- a/spec/requests/api/access_requests_spec.rb
+++ b/spec/requests/api/access_requests_spec.rb
@@ -48,6 +48,7 @@ describe API::AccessRequests, api: true do
get api("/#{source_type.pluralize}/#{source.id}/access_requests", master)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to eq(1)
end
diff --git a/spec/requests/api/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb
index c8e8f31cc1f..6cc1ef315db 100644
--- a/spec/requests/api/award_emoji_spec.rb
+++ b/spec/requests/api/award_emoji_spec.rb
@@ -34,6 +34,7 @@ describe API::AwardEmoji, api: true do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['name']).to eq(downvote.name)
end
diff --git a/spec/requests/api/boards_spec.rb b/spec/requests/api/boards_spec.rb
index c14c3cb1ce7..71df534ebe1 100644
--- a/spec/requests/api/boards_spec.rb
+++ b/spec/requests/api/boards_spec.rb
@@ -55,6 +55,7 @@ describe API::Boards, api: true do
get api(base_url, user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(board.id)
@@ -72,6 +73,7 @@ describe API::Boards, api: true do
get api(base_url, user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
expect(json_response.first['label']['name']).to eq(dev_label.title)
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index 5a3ffc284f2..cacdb21c692 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -17,8 +17,10 @@ describe API::Branches, api: true do
it "returns an array of project branches" do
project.repository.expire_all_method_caches
- get api("/projects/#{project.id}/repository/branches", user)
+ get api("/projects/#{project.id}/repository/branches", user), per_page: 100
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
branch_names = json_response.map { |x| x['name'] }
expect(branch_names).to match_array(project.repository.branch_names)
@@ -31,7 +33,18 @@ describe API::Branches, api: true do
expect(response).to have_http_status(200)
expect(json_response['name']).to eq(branch_name)
- expect(json_response['commit']['id']).to eq(branch_sha)
+ json_commit = json_response['commit']
+ expect(json_commit['id']).to eq(branch_sha)
+ expect(json_commit).to have_key('short_id')
+ expect(json_commit).to have_key('title')
+ expect(json_commit).to have_key('message')
+ expect(json_commit).to have_key('author_name')
+ expect(json_commit).to have_key('author_email')
+ expect(json_commit).to have_key('authored_date')
+ expect(json_commit).to have_key('committer_name')
+ expect(json_commit).to have_key('committer_email')
+ expect(json_commit).to have_key('committed_date')
+ expect(json_commit).to have_key('parent_ids')
expect(json_response['merged']).to eq(false)
expect(json_response['protected']).to eq(false)
expect(json_response['developers_can_push']).to eq(false)
@@ -259,7 +272,7 @@ describe API::Branches, api: true do
describe "POST /projects/:id/repository/branches" do
it "creates a new branch" do
post api("/projects/#{project.id}/repository/branches", user),
- branch_name: 'feature1',
+ branch: 'feature1',
ref: branch_sha
expect(response).to have_http_status(201)
@@ -270,14 +283,14 @@ describe API::Branches, api: true do
it "denies for user without push access" do
post api("/projects/#{project.id}/repository/branches", user2),
- branch_name: branch_name,
+ branch: branch_name,
ref: branch_sha
expect(response).to have_http_status(403)
end
it 'returns 400 if branch name is invalid' do
post api("/projects/#{project.id}/repository/branches", user),
- branch_name: 'new design',
+ branch: 'new design',
ref: branch_sha
expect(response).to have_http_status(400)
expect(json_response['message']).to eq('Branch name is invalid')
@@ -285,12 +298,12 @@ describe API::Branches, api: true do
it 'returns 400 if branch already exists' do
post api("/projects/#{project.id}/repository/branches", user),
- branch_name: 'new_design1',
+ branch: 'new_design1',
ref: branch_sha
expect(response).to have_http_status(201)
post api("/projects/#{project.id}/repository/branches", user),
- branch_name: 'new_design1',
+ branch: 'new_design1',
ref: branch_sha
expect(response).to have_http_status(400)
expect(json_response['message']).to eq('Branch already exists')
@@ -298,7 +311,7 @@ describe API::Branches, api: true do
it 'returns 400 if ref name is invalid' do
post api("/projects/#{project.id}/repository/branches", user),
- branch_name: 'new_design3',
+ branch: 'new_design3',
ref: 'foo'
expect(response).to have_http_status(400)
expect(json_response['message']).to eq('Invalid reference name')
@@ -313,14 +326,14 @@ describe API::Branches, api: true do
it "removes branch" do
delete api("/projects/#{project.id}/repository/branches/#{branch_name}", user)
expect(response).to have_http_status(200)
- expect(json_response['branch_name']).to eq(branch_name)
+ expect(json_response['branch']).to eq(branch_name)
end
it "removes a branch with dots in the branch name" do
delete api("/projects/#{project.id}/repository/branches/with.1.2.3", user)
expect(response).to have_http_status(200)
- expect(json_response['branch_name']).to eq("with.1.2.3")
+ expect(json_response['branch']).to eq("with.1.2.3")
end
it 'returns 404 if branch not exists' do
@@ -347,9 +360,11 @@ describe API::Branches, api: true do
allow_any_instance_of(Repository).to receive(:rm_branch).and_return(true)
end
- it 'returns 200' do
+ it 'returns 202 with json body' do
delete api("/projects/#{project.id}/repository/merged_branches", user)
- expect(response).to have_http_status(200)
+
+ expect(response).to have_http_status(202)
+ expect(json_response['message']).to eql('202 Accepted')
end
it 'returns a 403 error if guest' do
diff --git a/spec/requests/api/broadcast_messages_spec.rb b/spec/requests/api/broadcast_messages_spec.rb
index 7c9078b2864..921d8714173 100644
--- a/spec/requests/api/broadcast_messages_spec.rb
+++ b/spec/requests/api/broadcast_messages_spec.rb
@@ -25,6 +25,7 @@ describe API::BroadcastMessages, api: true do
get api('/broadcast_messages', admin)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_kind_of(Array)
expect(json_response.first.keys)
.to match_array(%w(id message starts_at ends_at color font active))
diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb
index 834c4e52693..76a10a2374c 100644
--- a/spec/requests/api/builds_spec.rb
+++ b/spec/requests/api/builds_spec.rb
@@ -16,12 +16,15 @@ describe API::Builds, api: true do
let(:query) { '' }
before do
+ create(:ci_build, :skipped, pipeline: pipeline)
+
get api("/projects/#{project.id}/builds?#{query}", api_user)
end
context 'authorized user' do
it 'returns project builds' do
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
end
@@ -48,6 +51,18 @@ describe API::Builds, api: true do
end
end
+ context 'filter project with scope skipped' do
+ let(:query) { 'scope=skipped' }
+ let(:json_build) { json_response.first }
+
+ it 'return builds with status skipped' do
+ expect(response).to have_http_status 200
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq 1
+ expect(json_build['status']).to eq 'skipped'
+ end
+ end
+
context 'filter project with array of scope elements' do
let(:query) { 'scope[0]=pending&scope[1]=running' }
@@ -97,6 +112,7 @@ describe API::Builds, api: true do
it 'returns project jobs for specific commit' do
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to eq 2
end
diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb
index 88361def3cf..81a8856b8f1 100644
--- a/spec/requests/api/commit_statuses_spec.rb
+++ b/spec/requests/api/commit_statuses_spec.rb
@@ -5,12 +5,15 @@ describe API::CommitStatuses, api: true do
let!(:project) { create(:project, :repository) }
let(:commit) { project.repository.commit }
- let(:commit_status) { create(:commit_status, pipeline: pipeline) }
let(:guest) { create_user(:guest) }
let(:reporter) { create_user(:reporter) }
let(:developer) { create_user(:developer) }
let(:sha) { commit.id }
+ let(:commit_status) do
+ create(:commit_status, status: :pending, pipeline: pipeline)
+ end
+
describe "GET /projects/:id/repository/commits/:sha/statuses" do
let(:get_url) { "/projects/#{project.id}/repository/commits/#{sha}/statuses" }
@@ -18,10 +21,6 @@ describe API::CommitStatuses, api: true do
let!(:master) { project.pipelines.create(sha: commit.id, ref: 'master') }
let!(:develop) { project.pipelines.create(sha: commit.id, ref: 'develop') }
- it_behaves_like 'a paginated resources' do
- let(:request) { get api(get_url, reporter) }
- end
-
context "reporter user" do
let(:statuses_id) { json_response.map { |status| status['id'] } }
@@ -42,6 +41,7 @@ describe API::CommitStatuses, api: true do
it 'returns latest commit statuses' do
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(statuses_id).to contain_exactly(status3.id, status4.id, status5.id, status6.id)
json_response.sort_by!{ |status| status['id'] }
@@ -54,7 +54,7 @@ describe API::CommitStatuses, api: true do
it 'returns all commit statuses' do
expect(response).to have_http_status(200)
-
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(statuses_id).to contain_exactly(status1.id, status2.id,
status3.id, status4.id,
@@ -67,7 +67,7 @@ describe API::CommitStatuses, api: true do
it 'returns latest commit statuses for specific ref' do
expect(response).to have_http_status(200)
-
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(statuses_id).to contain_exactly(status3.id, status5.id)
end
@@ -78,7 +78,7 @@ describe API::CommitStatuses, api: true do
it 'return latest commit statuses for specific name' do
expect(response).to have_http_status(200)
-
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(statuses_id).to contain_exactly(status4.id, status5.id)
end
@@ -156,6 +156,7 @@ describe API::CommitStatuses, api: true do
context: 'coverage',
ref: 'develop',
description: 'test',
+ coverage: 80.0,
target_url: 'http://gitlab.com/status' }
post api(post_url, developer), optional_params
@@ -167,6 +168,7 @@ describe API::CommitStatuses, api: true do
expect(json_response['status']).to eq('success')
expect(json_response['name']).to eq('coverage')
expect(json_response['ref']).to eq('develop')
+ expect(json_response['coverage']).to eq(80.0)
expect(json_response['description']).to eq('test')
expect(json_response['target_url']).to eq('http://gitlab.com/status')
end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index af9028a8978..5190fcca2d1 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -72,7 +72,7 @@ describe API::Commits, api: true do
get api("/projects/#{project.id}/repository/commits?since=invalid-date", user)
expect(response).to have_http_status(400)
- expect(json_response['message']).to include "\"since\" must be a timestamp in ISO 8601 format"
+ expect(json_response['error']).to eq('since is invalid')
end
end
@@ -107,7 +107,7 @@ describe API::Commits, api: true do
let(:message) { 'Created file' }
let!(:invalid_c_params) do
{
- branch_name: 'master',
+ branch: 'master',
commit_message: message,
actions: [
{
@@ -120,7 +120,7 @@ describe API::Commits, api: true do
end
let!(:valid_c_params) do
{
- branch_name: 'master',
+ branch: 'master',
commit_message: message,
actions: [
{
@@ -148,7 +148,7 @@ describe API::Commits, api: true do
end
context 'with project path in URL' do
- let(:url) { "/projects/#{project.namespace.path}%2F#{project.path}/repository/commits" }
+ let(:url) { "/projects/#{project.full_path.gsub('/', '%2F')}/repository/commits" }
it 'a new file in project repo' do
post api(url, user), valid_c_params
@@ -162,7 +162,7 @@ describe API::Commits, api: true do
let(:message) { 'Deleted file' }
let!(:invalid_d_params) do
{
- branch_name: 'markdown',
+ branch: 'markdown',
commit_message: message,
actions: [
{
@@ -174,7 +174,7 @@ describe API::Commits, api: true do
end
let!(:valid_d_params) do
{
- branch_name: 'markdown',
+ branch: 'markdown',
commit_message: message,
actions: [
{
@@ -203,7 +203,7 @@ describe API::Commits, api: true do
let(:message) { 'Moved file' }
let!(:invalid_m_params) do
{
- branch_name: 'feature',
+ branch: 'feature',
commit_message: message,
actions: [
{
@@ -217,7 +217,7 @@ describe API::Commits, api: true do
end
let!(:valid_m_params) do
{
- branch_name: 'feature',
+ branch: 'feature',
commit_message: message,
actions: [
{
@@ -248,7 +248,7 @@ describe API::Commits, api: true do
let(:message) { 'Updated file' }
let!(:invalid_u_params) do
{
- branch_name: 'master',
+ branch: 'master',
commit_message: message,
actions: [
{
@@ -261,7 +261,7 @@ describe API::Commits, api: true do
end
let!(:valid_u_params) do
{
- branch_name: 'master',
+ branch: 'master',
commit_message: message,
actions: [
{
@@ -291,7 +291,7 @@ describe API::Commits, api: true do
let(:message) { 'Multiple actions' }
let!(:invalid_mo_params) do
{
- branch_name: 'master',
+ branch: 'master',
commit_message: message,
actions: [
{
@@ -319,7 +319,7 @@ describe API::Commits, api: true do
end
let!(:valid_mo_params) do
{
- branch_name: 'master',
+ branch: 'master',
commit_message: message,
actions: [
{
@@ -367,11 +367,21 @@ describe API::Commits, api: true do
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
expect(response).to have_http_status(200)
- expect(json_response['id']).to eq(project.repository.commit.id)
- expect(json_response['title']).to eq(project.repository.commit.title)
- expect(json_response['stats']['additions']).to eq(project.repository.commit.stats.additions)
- expect(json_response['stats']['deletions']).to eq(project.repository.commit.stats.deletions)
- expect(json_response['stats']['total']).to eq(project.repository.commit.stats.total)
+ commit = project.repository.commit
+ expect(json_response['id']).to eq(commit.id)
+ expect(json_response['short_id']).to eq(commit.short_id)
+ expect(json_response['title']).to eq(commit.title)
+ expect(json_response['message']).to eq(commit.safe_message)
+ expect(json_response['author_name']).to eq(commit.author_name)
+ expect(json_response['author_email']).to eq(commit.author_email)
+ expect(json_response['authored_date']).to eq(commit.authored_date.iso8601(3))
+ expect(json_response['committer_name']).to eq(commit.committer_name)
+ expect(json_response['committer_email']).to eq(commit.committer_email)
+ expect(json_response['committed_date']).to eq(commit.committed_date.iso8601(3))
+ expect(json_response['parent_ids']).to eq(commit.parent_ids)
+ expect(json_response['stats']['additions']).to eq(commit.stats.additions)
+ expect(json_response['stats']['deletions']).to eq(commit.stats.deletions)
+ expect(json_response['stats']['total']).to eq(commit.stats.total)
end
it "returns a 404 error if not found" do
@@ -446,6 +456,7 @@ describe API::Commits, api: true do
it 'returns merge_request comments' do
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
expect(json_response.first['note']).to eq('a comment on a commit')
@@ -464,6 +475,20 @@ describe API::Commits, api: true do
expect(response).to have_http_status(401)
end
end
+
+ context 'when the commit is present on two projects' do
+ let(:forked_project) { create(:project, :repository, creator: user2, namespace: user2.namespace) }
+ let!(:forked_project_note) { create(:note_on_commit, author: user2, project: forked_project, commit_id: forked_project.repository.commit.id, note: 'a comment on a commit for fork') }
+
+ it 'returns the comments for the target project' do
+ get api("/projects/#{forked_project.id}/repository/commits/#{forked_project.repository.commit.id}/comments", user2)
+
+ expect(response).to have_http_status(200)
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['note']).to eq('a comment on a commit for fork')
+ expect(json_response.first['author']['id']).to eq(user2.id)
+ end
+ end
end
describe 'POST :id/repository/commits/:sha/cherry_pick' do
diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb
index 766234d7104..7e682e91bd1 100644
--- a/spec/requests/api/deploy_keys_spec.rb
+++ b/spec/requests/api/deploy_keys_spec.rb
@@ -35,6 +35,7 @@ describe API::DeployKeys, api: true do
get api('/deploy_keys', admin)
expect(response.status).to eq(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['id']).to eq(deploy_keys_project.deploy_key.id)
end
@@ -48,6 +49,7 @@ describe API::DeployKeys, api: true do
get api("/projects/#{project.id}/deploy_keys", admin)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['title']).to eq(deploy_key.title)
end
@@ -146,25 +148,4 @@ describe API::DeployKeys, api: true do
end
end
end
-
- describe 'DELETE /projects/:id/deploy_keys/:key_id/disable' do
- context 'when the user can admin the project' do
- it 'disables the key' do
- expect do
- delete api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}/disable", admin)
- end.to change { project.deploy_keys.count }.from(1).to(0)
-
- expect(response).to have_http_status(200)
- expect(json_response['id']).to eq(deploy_key.id)
- end
- end
-
- context 'when authenticated as non-admin user' do
- it 'should return a 404 error' do
- delete api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}/disable", user)
-
- expect(response).to have_http_status(404)
- end
- end
- end
end
diff --git a/spec/requests/api/deployments_spec.rb b/spec/requests/api/deployments_spec.rb
index 31e3cfa1b2f..e55575ffbda 100644
--- a/spec/requests/api/deployments_spec.rb
+++ b/spec/requests/api/deployments_spec.rb
@@ -14,14 +14,11 @@ describe API::Deployments, api: true do
describe 'GET /projects/:id/deployments' do
context 'as member of the project' do
- it_behaves_like 'a paginated resources' do
- let(:request) { get api("/projects/#{project.id}/deployments", user) }
- end
-
it 'returns projects deployments' do
get api("/projects/#{project.id}/deployments", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to eq(1)
expect(json_response.first['iid']).to eq(deployment.iid)
diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb
index 8168b613766..d0958d39d44 100644
--- a/spec/requests/api/environments_spec.rb
+++ b/spec/requests/api/environments_spec.rb
@@ -14,14 +14,11 @@ describe API::Environments, api: true do
describe 'GET /projects/:id/environments' do
context 'as member of the project' do
- it_behaves_like 'a paginated resources' do
- let(:request) { get api("/projects/#{project.id}/environments", user) }
- end
-
it 'returns project environments' do
get api("/projects/#{project.id}/environments", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to eq(1)
expect(json_response.first['name']).to eq(environment.name)
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index 5e26e779366..a8ce0430401 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -104,7 +104,7 @@ describe API::Files, api: true do
let(:valid_params) do
{
file_path: 'newfile.rb',
- branch_name: 'master',
+ branch: 'master',
content: 'puts 8',
commit_message: 'Added newfile'
}
@@ -153,7 +153,7 @@ describe API::Files, api: true do
let(:valid_params) do
{
file_path: file_path,
- branch_name: 'master',
+ branch: 'master',
content: 'puts 8',
commit_message: 'Changed file'
}
@@ -193,7 +193,7 @@ describe API::Files, api: true do
let(:valid_params) do
{
file_path: file_path,
- branch_name: 'master',
+ branch: 'master',
commit_message: 'Changed file'
}
end
@@ -241,7 +241,7 @@ describe API::Files, api: true do
let(:put_params) do
{
file_path: file_path,
- branch_name: 'master',
+ branch: 'master',
content: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=',
commit_message: 'Binary file with a \n should not be touched',
encoding: 'base64'
diff --git a/spec/requests/api/fork_spec.rb b/spec/requests/api/fork_spec.rb
deleted file mode 100644
index 92ac4fd334d..00000000000
--- a/spec/requests/api/fork_spec.rb
+++ /dev/null
@@ -1,134 +0,0 @@
-require 'spec_helper'
-
-describe API::Projects, api: true do
- include ApiHelpers
- let(:user) { create(:user) }
- let(:user2) { create(:user) }
- let(:admin) { create(:admin) }
- let(:group) { create(:group) }
- let(:group2) do
- group = create(:group, name: 'group2_name')
- group.add_owner(user2)
- group
- end
-
- describe 'POST /projects/fork/:id' do
- let(:project) do
- create(:project, :repository, creator: user, namespace: user.namespace)
- end
-
- before do
- project.add_reporter(user2)
- end
-
- context 'when authenticated' do
- it 'forks if user has sufficient access to project' do
- post api("/projects/fork/#{project.id}", user2)
-
- expect(response).to have_http_status(201)
- expect(json_response['name']).to eq(project.name)
- expect(json_response['path']).to eq(project.path)
- expect(json_response['owner']['id']).to eq(user2.id)
- expect(json_response['namespace']['id']).to eq(user2.namespace.id)
- expect(json_response['forked_from_project']['id']).to eq(project.id)
- end
-
- it 'forks if user is admin' do
- post api("/projects/fork/#{project.id}", admin)
-
- expect(response).to have_http_status(201)
- expect(json_response['name']).to eq(project.name)
- expect(json_response['path']).to eq(project.path)
- expect(json_response['owner']['id']).to eq(admin.id)
- expect(json_response['namespace']['id']).to eq(admin.namespace.id)
- expect(json_response['forked_from_project']['id']).to eq(project.id)
- end
-
- it 'fails on missing project access for the project to fork' do
- new_user = create(:user)
- post api("/projects/fork/#{project.id}", new_user)
-
- expect(response).to have_http_status(404)
- expect(json_response['message']).to eq('404 Project Not Found')
- end
-
- it 'fails if forked project exists in the user namespace' do
- post api("/projects/fork/#{project.id}", user)
-
- expect(response).to have_http_status(409)
- expect(json_response['message']['name']).to eq(['has already been taken'])
- expect(json_response['message']['path']).to eq(['has already been taken'])
- end
-
- it 'fails if project to fork from does not exist' do
- post api('/projects/fork/424242', user)
-
- expect(response).to have_http_status(404)
- expect(json_response['message']).to eq('404 Project Not Found')
- end
-
- it 'forks with explicit own user namespace id' do
- post api("/projects/fork/#{project.id}", user2), namespace: user2.namespace.id
-
- expect(response).to have_http_status(201)
- expect(json_response['owner']['id']).to eq(user2.id)
- end
-
- it 'forks with explicit own user name as namespace' do
- post api("/projects/fork/#{project.id}", user2), namespace: user2.username
-
- expect(response).to have_http_status(201)
- expect(json_response['owner']['id']).to eq(user2.id)
- end
-
- it 'forks to another user when admin' do
- post api("/projects/fork/#{project.id}", admin), namespace: user2.username
-
- expect(response).to have_http_status(201)
- expect(json_response['owner']['id']).to eq(user2.id)
- end
-
- it 'fails if trying to fork to another user when not admin' do
- post api("/projects/fork/#{project.id}", user2), namespace: admin.namespace.id
-
- expect(response).to have_http_status(404)
- end
-
- it 'fails if trying to fork to non-existent namespace' do
- post api("/projects/fork/#{project.id}", user2), namespace: 42424242
-
- expect(response).to have_http_status(404)
- expect(json_response['message']).to eq('404 Target Namespace Not Found')
- end
-
- it 'forks to owned group' do
- post api("/projects/fork/#{project.id}", user2), namespace: group2.name
-
- expect(response).to have_http_status(201)
- expect(json_response['namespace']['name']).to eq(group2.name)
- end
-
- it 'fails to fork to not owned group' do
- post api("/projects/fork/#{project.id}", user2), namespace: group.name
-
- expect(response).to have_http_status(404)
- end
-
- it 'forks to not owned group when admin' do
- post api("/projects/fork/#{project.id}", admin), namespace: group.name
-
- expect(response).to have_http_status(201)
- expect(json_response['namespace']['name']).to eq(group.name)
- end
- end
-
- context 'when unauthenticated' do
- it 'returns authentication error' do
- post api("/projects/fork/#{project.id}")
-
- expect(response).to have_http_status(401)
- expect(json_response['message']).to eq('401 Unauthorized')
- end
- end
- end
-end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index a027c23bb88..fb3dc1b074e 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -33,15 +33,18 @@ describe API::Groups, api: true do
get api("/groups", user1)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
- expect(json_response.first['name']).to eq(group1.name)
+ expect(json_response)
+ .to satisfy_one { |group| group['name'] == group1.name }
end
it "does not include statistics" do
get api("/groups", user1), statistics: true
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first).not_to include 'statistics'
end
@@ -52,6 +55,7 @@ describe API::Groups, api: true do
get api("/groups", admin)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
end
@@ -60,6 +64,7 @@ describe API::Groups, api: true do
get api("/groups", admin)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first).not_to include('statistics')
end
@@ -70,15 +75,17 @@ describe API::Groups, api: true do
repository_size: 123,
lfs_objects_size: 234,
build_artifacts_size: 345,
- }
+ }.stringify_keys
project1.statistics.update!(attributes)
get api("/groups", admin), statistics: true
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(json_response.first['statistics']).to eq attributes.stringify_keys
+ expect(json_response)
+ .to satisfy_one { |group| group['statistics'] == attributes }
end
end
@@ -87,6 +94,7 @@ describe API::Groups, api: true do
get api("/groups", admin), skip_groups: [group2.id]
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
end
@@ -101,6 +109,7 @@ describe API::Groups, api: true do
get api("/groups", user1), all_available: true
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_groups).to contain_exactly(public_group.name, group1.name)
end
@@ -118,6 +127,7 @@ describe API::Groups, api: true do
get api("/groups", user1)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_groups).to eq([group3.name, group1.name])
end
@@ -126,6 +136,7 @@ describe API::Groups, api: true do
get api("/groups", user1), sort: "desc"
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_groups).to eq([group1.name, group3.name])
end
@@ -134,26 +145,18 @@ describe API::Groups, api: true do
get api("/groups", user1), order_by: "path"
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_groups).to eq([group1.name, group3.name])
end
end
- end
-
- describe 'GET /groups/owned' do
- context 'when unauthenticated' do
- it 'returns authentication error' do
- get api('/groups/owned')
- expect(response).to have_http_status(401)
- end
- end
-
- context 'when authenticated as group owner' do
+ context 'when using owned in the request' do
it 'returns an array of groups the user owns' do
- get api('/groups/owned', user2)
+ get api('/groups', user2), owned: true
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['name']).to eq(group2.name)
end
@@ -179,6 +182,7 @@ describe API::Groups, api: true do
expect(json_response['request_access_enabled']).to eq(group1.request_access_enabled)
expect(json_response['full_name']).to eq(group1.full_name)
expect(json_response['full_path']).to eq(group1.full_path)
+ expect(json_response['parent_id']).to eq(group1.parent_id)
expect(json_response['projects']).to be_an Array
expect(json_response['projects'].length).to eq(2)
expect(json_response['shared_projects']).to be_an Array
@@ -287,8 +291,9 @@ describe API::Groups, api: true do
get api("/groups/#{group1.id}/projects", user1)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response.length).to eq(2)
- project_names = json_response.map { |proj| proj['name' ] }
+ project_names = json_response.map { |proj| proj['name'] }
expect(project_names).to match_array([project1.name, project3.name])
expect(json_response.first['visibility_level']).to be_present
end
@@ -297,8 +302,9 @@ describe API::Groups, api: true do
get api("/groups/#{group1.id}/projects", user1), simple: true
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response.length).to eq(2)
- project_names = json_response.map { |proj| proj['name' ] }
+ project_names = json_response.map { |proj| proj['name'] }
expect(project_names).to match_array([project1.name, project3.name])
expect(json_response.first['visibility_level']).not_to be_present
end
@@ -309,6 +315,7 @@ describe API::Groups, api: true do
get api("/groups/#{group1.id}/projects", user1), visibility: 'public'
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an(Array)
expect(json_response.length).to eq(1)
expect(json_response.first['name']).to eq(public_project.name)
@@ -332,9 +339,30 @@ describe API::Groups, api: true do
get api("/groups/#{group1.id}/projects", user3)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response.length).to eq(1)
expect(json_response.first['name']).to eq(project3.name)
end
+
+ it 'only returns the projects owned by user' do
+ project2.group.add_owner(user3)
+
+ get api("/groups/#{project2.group.id}/projects", user3), owned: true
+
+ expect(response).to have_http_status(200)
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['name']).to eq(project2.name)
+ end
+
+ it 'only returns the projects starred by user' do
+ user1.starred_projects = [project1]
+
+ get api("/groups/#{group1.id}/projects", user1), starred: true
+
+ expect(response).to have_http_status(200)
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['name']).to eq(project1.name)
+ end
end
context "when authenticated as admin" do
@@ -342,6 +370,7 @@ describe API::Groups, api: true do
get api("/groups/#{group2.id}/projects", admin)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response.length).to eq(1)
expect(json_response.first['name']).to eq(project2.name)
end
@@ -358,7 +387,8 @@ describe API::Groups, api: true do
get api("/groups/#{group1.path}/projects", admin)
expect(response).to have_http_status(200)
- project_names = json_response.map { |proj| proj['name' ] }
+ expect(response).to include_pagination_headers
+ project_names = json_response.map { |proj| proj['name'] }
expect(project_names).to match_array([project1.name, project3.name])
end
@@ -398,6 +428,19 @@ describe API::Groups, api: true do
expect(json_response["request_access_enabled"]).to eq(group[:request_access_enabled])
end
+ it "creates a nested group" do
+ parent = create(:group)
+ parent.add_owner(user3)
+ group = attributes_for(:group, { parent_id: parent.id })
+
+ post api("/groups", user3), group
+
+ expect(response).to have_http_status(201)
+
+ expect(json_response["full_path"]).to eq("#{parent.path}/#{group[:path]}")
+ expect(json_response["parent_id"]).to eq(parent.id)
+ end
+
it "does not create group, duplicate" do
post api("/groups", user3), { name: 'Duplicate Test', path: group2.path }
@@ -466,7 +509,7 @@ describe API::Groups, api: true do
describe "POST /groups/:id/projects/:project_id" do
let(:project) { create(:empty_project) }
- let(:project_path) { "#{project.namespace.path}%2F#{project.path}" }
+ let(:project_path) { project.full_path.gsub('/', '%2F') }
before(:each) do
allow_any_instance_of(Projects::TransferService).
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index cca00df9591..7cb75310204 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -68,7 +68,9 @@ describe API::Issues, api: true do
context "when authenticated" do
it "returns an array of issues" do
get api("/issues", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['title']).to eq(issue.title)
expect(json_response.last).to have_key('web_url')
@@ -76,7 +78,9 @@ describe API::Issues, api: true do
it 'returns an array of closed issues' do
get api('/issues?state=closed', user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(closed_issue.id)
@@ -84,7 +88,9 @@ describe API::Issues, api: true do
it 'returns an array of opened issues' do
get api('/issues?state=opened', user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(issue.id)
@@ -92,7 +98,9 @@ describe API::Issues, api: true do
it 'returns an array of all issues' do
get api('/issues?state=all', user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
expect(json_response.first['id']).to eq(issue.id)
@@ -101,31 +109,44 @@ describe API::Issues, api: true do
it 'returns an array of labeled issues' do
get api("/issues?labels=#{label.title}", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['labels']).to eq([label.title])
end
- it 'returns an array of labeled issues when at least one label matches' do
- get api("/issues?labels=#{label.title},foo,bar", user)
+ it 'returns an array of labeled issues when all labels matches' do
+ label_b = create(:label, title: 'foo', project: project)
+ label_c = create(:label, title: 'bar', project: project)
+
+ create(:label_link, label: label_b, target: issue)
+ create(:label_link, label: label_c, target: issue)
+
+ get api("/issues", user), labels: "#{label.title},#{label_b.title},#{label_c.title}"
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
- expect(json_response.first['labels']).to eq([label.title])
+ expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title])
end
it 'returns an empty array if no issue matches labels' do
get api('/issues?labels=foo,bar', user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
it 'returns an array of labeled issues matching given state' do
get api("/issues?labels=#{label.title}&state=opened", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['labels']).to eq([label.title])
@@ -134,7 +155,9 @@ describe API::Issues, api: true do
it 'returns an empty array if no issue matches labels and state filters' do
get api("/issues?labels=#{label.title}&state=closed", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
@@ -143,6 +166,7 @@ describe API::Issues, api: true do
get api("/issues?milestone=#{empty_milestone.title}", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
@@ -151,6 +175,7 @@ describe API::Issues, api: true do
get api("/issues?milestone=foo", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
@@ -159,6 +184,7 @@ describe API::Issues, api: true do
get api("/issues?milestone=#{milestone.title}", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
expect(json_response.first['id']).to eq(issue.id)
@@ -170,6 +196,7 @@ describe API::Issues, api: true do
'&state=closed', user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(closed_issue.id)
@@ -179,6 +206,7 @@ describe API::Issues, api: true do
get api("/issues?milestone=#{no_milestone_title}", author)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(confidential_issue.id)
@@ -186,36 +214,40 @@ describe API::Issues, api: true do
it 'sorts by created_at descending by default' do
get api('/issues', user)
- response_dates = json_response.map { |issue| issue['created_at'] }
+ response_dates = json_response.map { |issue| issue['created_at'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_dates).to eq(response_dates.sort.reverse)
end
it 'sorts ascending when requested' do
get api('/issues?sort=asc', user)
- response_dates = json_response.map { |issue| issue['created_at'] }
+ response_dates = json_response.map { |issue| issue['created_at'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_dates).to eq(response_dates.sort)
end
it 'sorts by updated_at descending when requested' do
get api('/issues?order_by=updated_at', user)
- response_dates = json_response.map { |issue| issue['updated_at'] }
+ response_dates = json_response.map { |issue| issue['updated_at'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_dates).to eq(response_dates.sort.reverse)
end
it 'sorts by updated_at ascending when requested' do
get api('/issues?order_by=updated_at&sort=asc', user)
- response_dates = json_response.map { |issue| issue['updated_at'] }
+ response_dates = json_response.map { |issue| issue['updated_at'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_dates).to eq(response_dates.sort)
end
@@ -269,6 +301,7 @@ describe API::Issues, api: true do
get api(base_url, non_member)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['title']).to eq(group_issue.title)
@@ -278,6 +311,7 @@ describe API::Issues, api: true do
get api(base_url, author)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
end
@@ -286,6 +320,7 @@ describe API::Issues, api: true do
get api(base_url, assignee)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
end
@@ -294,6 +329,7 @@ describe API::Issues, api: true do
get api(base_url, user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
end
@@ -302,6 +338,7 @@ describe API::Issues, api: true do
get api(base_url, admin)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
end
@@ -310,6 +347,7 @@ describe API::Issues, api: true do
get api("#{base_url}?labels=#{group_label.title}", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['labels']).to eq([group_label.title])
@@ -319,14 +357,31 @@ describe API::Issues, api: true do
get api("#{base_url}?labels=#{group_label.title},foo,bar", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
+ it 'returns an array of labeled issues when all labels matches' do
+ label_b = create(:label, title: 'foo', project: group_project)
+ label_c = create(:label, title: 'bar', project: group_project)
+
+ create(:label_link, label: label_b, target: group_issue)
+ create(:label_link, label: label_c, target: group_issue)
+
+ get api("#{base_url}", user), labels: "#{group_label.title},#{label_b.title},#{label_c.title}"
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['labels']).to eq([label_c.title, label_b.title, group_label.title])
+ end
+
it 'returns an empty array if no group issue matches labels' do
get api("#{base_url}?labels=foo,bar", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
@@ -335,6 +390,7 @@ describe API::Issues, api: true do
get api("#{base_url}?milestone=#{group_empty_milestone.title}", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
@@ -343,6 +399,7 @@ describe API::Issues, api: true do
get api("#{base_url}?milestone=foo", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
@@ -351,6 +408,7 @@ describe API::Issues, api: true do
get api("#{base_url}?milestone=#{group_milestone.title}", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(group_issue.id)
@@ -361,6 +419,7 @@ describe API::Issues, api: true do
'&state=closed', user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(group_closed_issue.id)
@@ -370,6 +429,7 @@ describe API::Issues, api: true do
get api("#{base_url}?milestone=#{no_milestone_title}", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(group_confidential_issue.id)
@@ -377,36 +437,40 @@ describe API::Issues, api: true do
it 'sorts by created_at descending by default' do
get api(base_url, user)
- response_dates = json_response.map { |issue| issue['created_at'] }
+ response_dates = json_response.map { |issue| issue['created_at'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_dates).to eq(response_dates.sort.reverse)
end
it 'sorts ascending when requested' do
get api("#{base_url}?sort=asc", user)
- response_dates = json_response.map { |issue| issue['created_at'] }
+ response_dates = json_response.map { |issue| issue['created_at'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_dates).to eq(response_dates.sort)
end
it 'sorts by updated_at descending when requested' do
get api("#{base_url}?order_by=updated_at", user)
- response_dates = json_response.map { |issue| issue['updated_at'] }
+ response_dates = json_response.map { |issue| issue['updated_at'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_dates).to eq(response_dates.sort.reverse)
end
it 'sorts by updated_at ascending when requested' do
get api("#{base_url}?order_by=updated_at&sort=asc", user)
- response_dates = json_response.map { |issue| issue['updated_at'] }
+ response_dates = json_response.map { |issue| issue['updated_at'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_dates).to eq(response_dates.sort)
end
@@ -430,12 +494,17 @@ describe API::Issues, api: true do
get api("/projects/#{restricted_project.id}/issues", non_member)
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
expect(json_response).to eq([])
end
it 'returns project issues without confidential issues for non project members' do
get api("#{base_url}/issues", non_member)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
expect(json_response.first['title']).to eq(issue.title)
@@ -443,7 +512,9 @@ describe API::Issues, api: true do
it 'returns project issues without confidential issues for project members with guest role' do
get api("#{base_url}/issues", guest)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
expect(json_response.first['title']).to eq(issue.title)
@@ -451,7 +522,9 @@ describe API::Issues, api: true do
it 'returns project confidential issues for author' do
get api("#{base_url}/issues", author)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
expect(json_response.first['title']).to eq(issue.title)
@@ -459,7 +532,9 @@ describe API::Issues, api: true do
it 'returns project confidential issues for assignee' do
get api("#{base_url}/issues", assignee)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
expect(json_response.first['title']).to eq(issue.title)
@@ -467,7 +542,9 @@ describe API::Issues, api: true do
it 'returns project issues with confidential issues for project members' do
get api("#{base_url}/issues", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
expect(json_response.first['title']).to eq(issue.title)
@@ -475,7 +552,9 @@ describe API::Issues, api: true do
it 'returns project confidential issues for admin' do
get api("#{base_url}/issues", admin)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
expect(json_response.first['title']).to eq(issue.title)
@@ -483,38 +562,61 @@ describe API::Issues, api: true do
it 'returns an array of labeled project issues' do
get api("#{base_url}/issues?labels=#{label.title}", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['labels']).to eq([label.title])
end
- it 'returns an array of labeled project issues where all labels match' do
- get api("#{base_url}/issues?labels=#{label.title},foo,bar", user)
+ it 'returns an array of labeled issues when all labels matches' do
+ label_b = create(:label, title: 'foo', project: project)
+ label_c = create(:label, title: 'bar', project: project)
+
+ create(:label_link, label: label_b, target: issue)
+ create(:label_link, label: label_c, target: issue)
+
+ get api("#{base_url}/issues", user), labels: "#{label.title},#{label_b.title},#{label_c.title}"
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
- expect(json_response.first['labels']).to eq([label.title])
+ expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title])
+ end
+
+ it 'returns an empty array if not all labels matches' do
+ get api("#{base_url}/issues?labels=#{label.title},foo", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(0)
end
it 'returns an empty array if no project issue matches labels' do
get api("#{base_url}/issues?labels=foo,bar", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
it 'returns an empty array if no issue matches milestone' do
get api("#{base_url}/issues?milestone=#{empty_milestone.title}", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
it 'returns an empty array if milestone does not exist' do
get api("#{base_url}/issues?milestone=foo", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
@@ -523,6 +625,7 @@ describe API::Issues, api: true do
get api("#{base_url}/issues?milestone=#{milestone.title}", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
expect(json_response.first['id']).to eq(issue.id)
@@ -530,9 +633,10 @@ describe API::Issues, api: true do
end
it 'returns an array of issues matching state in milestone' do
- get api("#{base_url}/issues?milestone=#{milestone.title}"\
- '&state=closed', user)
+ get api("#{base_url}/issues?milestone=#{milestone.title}&state=closed", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(closed_issue.id)
@@ -542,6 +646,7 @@ describe API::Issues, api: true do
get api("#{base_url}/issues?milestone=#{no_milestone_title}", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(confidential_issue.id)
@@ -549,36 +654,40 @@ describe API::Issues, api: true do
it 'sorts by created_at descending by default' do
get api("#{base_url}/issues", user)
- response_dates = json_response.map { |issue| issue['created_at'] }
+ response_dates = json_response.map { |issue| issue['created_at'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_dates).to eq(response_dates.sort.reverse)
end
it 'sorts ascending when requested' do
get api("#{base_url}/issues?sort=asc", user)
- response_dates = json_response.map { |issue| issue['created_at'] }
+ response_dates = json_response.map { |issue| issue['created_at'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_dates).to eq(response_dates.sort)
end
it 'sorts by updated_at descending when requested' do
get api("#{base_url}/issues?order_by=updated_at", user)
- response_dates = json_response.map { |issue| issue['updated_at'] }
+ response_dates = json_response.map { |issue| issue['updated_at'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_dates).to eq(response_dates.sort.reverse)
end
it 'sorts by updated_at ascending when requested' do
get api("#{base_url}/issues?order_by=updated_at&sort=asc", user)
- response_dates = json_response.map { |issue| issue['updated_at'] }
+ response_dates = json_response.map { |issue| issue['updated_at'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_dates).to eq(response_dates.sort)
end
@@ -666,7 +775,7 @@ describe API::Issues, api: true do
expect(response).to have_http_status(201)
expect(json_response['title']).to eq('new issue')
expect(json_response['description']).to be_nil
- expect(json_response['labels']).to eq(['label', 'label2'])
+ expect(json_response['labels']).to eq(%w(label label2))
expect(json_response['confidential']).to be_falsy
end
@@ -919,6 +1028,33 @@ describe API::Issues, api: true do
end
end
+ describe 'PUT /projects/:id/issues/:issue_id with spam filtering' do
+ let(:params) do
+ {
+ title: 'updated title',
+ description: 'content here',
+ labels: 'label, label2'
+ }
+ end
+
+ it "does not create a new project issue" do
+ allow_any_instance_of(SpamService).to receive_messages(check_for_spam?: true)
+ allow_any_instance_of(AkismetService).to receive_messages(is_spam?: true)
+
+ put api("/projects/#{project.id}/issues/#{issue.id}", user), params
+
+ expect(response).to have_http_status(400)
+ expect(json_response['message']).to eq({ "error" => "Spam detected" })
+
+ spam_logs = SpamLog.all
+ expect(spam_logs.count).to eq(1)
+ expect(spam_logs[0].title).to eq('updated title')
+ expect(spam_logs[0].description).to eq('content here')
+ expect(spam_logs[0].user).to eq(user)
+ expect(spam_logs[0].noteable_type).to eq('Issue')
+ end
+ end
+
describe 'PUT /projects/:id/issues/:issue_id to update labels' do
let!(:label) { create(:label, title: 'dummy', project: project) }
let!(:label_link) { create(:label_link, label: label, target: issue) }
@@ -1123,55 +1259,55 @@ describe API::Issues, api: true do
end
end
- describe 'POST :id/issues/:issue_id/subscription' do
+ describe 'POST :id/issues/:issue_id/subscribe' do
it 'subscribes to an issue' do
- post api("/projects/#{project.id}/issues/#{issue.id}/subscription", user2)
+ post api("/projects/#{project.id}/issues/#{issue.id}/subscribe", user2)
expect(response).to have_http_status(201)
expect(json_response['subscribed']).to eq(true)
end
it 'returns 304 if already subscribed' do
- post api("/projects/#{project.id}/issues/#{issue.id}/subscription", user)
+ post api("/projects/#{project.id}/issues/#{issue.id}/subscribe", user)
expect(response).to have_http_status(304)
end
it 'returns 404 if the issue is not found' do
- post api("/projects/#{project.id}/issues/123/subscription", user)
+ post api("/projects/#{project.id}/issues/123/subscribe", user)
expect(response).to have_http_status(404)
end
it 'returns 404 if the issue is confidential' do
- post api("/projects/#{project.id}/issues/#{confidential_issue.id}/subscription", non_member)
+ post api("/projects/#{project.id}/issues/#{confidential_issue.id}/subscribe", non_member)
expect(response).to have_http_status(404)
end
end
- describe 'DELETE :id/issues/:issue_id/subscription' do
+ describe 'POST :id/issues/:issue_id/unsubscribe' do
it 'unsubscribes from an issue' do
- delete api("/projects/#{project.id}/issues/#{issue.id}/subscription", user)
+ post api("/projects/#{project.id}/issues/#{issue.id}/unsubscribe", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_http_status(201)
expect(json_response['subscribed']).to eq(false)
end
it 'returns 304 if not subscribed' do
- delete api("/projects/#{project.id}/issues/#{issue.id}/subscription", user2)
+ post api("/projects/#{project.id}/issues/#{issue.id}/unsubscribe", user2)
expect(response).to have_http_status(304)
end
it 'returns 404 if the issue is not found' do
- delete api("/projects/#{project.id}/issues/123/subscription", user)
+ post api("/projects/#{project.id}/issues/123/unsubscribe", user)
expect(response).to have_http_status(404)
end
it 'returns 404 if the issue is confidential' do
- delete api("/projects/#{project.id}/issues/#{confidential_issue.id}/subscription", non_member)
+ post api("/projects/#{project.id}/issues/#{confidential_issue.id}/unsubscribe", non_member)
expect(response).to have_http_status(404)
end
diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb
index a8cd787f398..af271dbd4f5 100644
--- a/spec/requests/api/labels_spec.rb
+++ b/spec/requests/api/labels_spec.rb
@@ -21,15 +21,16 @@ describe API::Labels, api: true do
create(:labeled_issue, project: project, labels: [label1], author: user, state: :closed)
create(:labeled_merge_request, labels: [priority_label], author: user, source_project: project )
- expected_keys = [
- 'id', 'name', 'color', 'description',
- 'open_issues_count', 'closed_issues_count', 'open_merge_requests_count',
- 'subscribed', 'priority'
- ]
+ expected_keys = %w(
+ id name color description
+ open_issues_count closed_issues_count open_merge_requests_count
+ subscribed priority
+ )
get api("/projects/#{project.id}/labels", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to eq(3)
expect(json_response.first.keys).to match_array expected_keys
@@ -317,10 +318,10 @@ describe API::Labels, api: true do
end
end
- describe "POST /projects/:id/labels/:label_id/subscription" do
+ describe "POST /projects/:id/labels/:label_id/subscribe" do
context "when label_id is a label title" do
it "subscribes to the label" do
- post api("/projects/#{project.id}/labels/#{label1.title}/subscription", user)
+ post api("/projects/#{project.id}/labels/#{label1.title}/subscribe", user)
expect(response).to have_http_status(201)
expect(json_response["name"]).to eq(label1.title)
@@ -330,7 +331,7 @@ describe API::Labels, api: true do
context "when label_id is a label ID" do
it "subscribes to the label" do
- post api("/projects/#{project.id}/labels/#{label1.id}/subscription", user)
+ post api("/projects/#{project.id}/labels/#{label1.id}/subscribe", user)
expect(response).to have_http_status(201)
expect(json_response["name"]).to eq(label1.title)
@@ -342,7 +343,7 @@ describe API::Labels, api: true do
before { label1.subscribe(user, project) }
it "returns 304" do
- post api("/projects/#{project.id}/labels/#{label1.id}/subscription", user)
+ post api("/projects/#{project.id}/labels/#{label1.id}/subscribe", user)
expect(response).to have_http_status(304)
end
@@ -350,21 +351,21 @@ describe API::Labels, api: true do
context "when label ID is not found" do
it "returns 404 error" do
- post api("/projects/#{project.id}/labels/1234/subscription", user)
+ post api("/projects/#{project.id}/labels/1234/subscribe", user)
expect(response).to have_http_status(404)
end
end
end
- describe "DELETE /projects/:id/labels/:label_id/subscription" do
+ describe "POST /projects/:id/labels/:label_id/unsubscribe" do
before { label1.subscribe(user, project) }
context "when label_id is a label title" do
it "unsubscribes from the label" do
- delete api("/projects/#{project.id}/labels/#{label1.title}/subscription", user)
+ post api("/projects/#{project.id}/labels/#{label1.title}/unsubscribe", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_http_status(201)
expect(json_response["name"]).to eq(label1.title)
expect(json_response["subscribed"]).to be_falsey
end
@@ -372,9 +373,9 @@ describe API::Labels, api: true do
context "when label_id is a label ID" do
it "unsubscribes from the label" do
- delete api("/projects/#{project.id}/labels/#{label1.id}/subscription", user)
+ post api("/projects/#{project.id}/labels/#{label1.id}/unsubscribe", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_http_status(201)
expect(json_response["name"]).to eq(label1.title)
expect(json_response["subscribed"]).to be_falsey
end
@@ -384,7 +385,7 @@ describe API::Labels, api: true do
before { label1.unsubscribe(user, project) }
it "returns 304" do
- delete api("/projects/#{project.id}/labels/#{label1.id}/subscription", user)
+ post api("/projects/#{project.id}/labels/#{label1.id}/unsubscribe", user)
expect(response).to have_http_status(304)
end
@@ -392,7 +393,7 @@ describe API::Labels, api: true do
context "when label ID is not found" do
it "returns 404 error" do
- delete api("/projects/#{project.id}/labels/1234/subscription", user)
+ post api("/projects/#{project.id}/labels/1234/unsubscribe", user)
expect(response).to have_http_status(404)
end
diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb
index 9892e014cb9..31166b50033 100644
--- a/spec/requests/api/members_spec.rb
+++ b/spec/requests/api/members_spec.rb
@@ -34,9 +34,12 @@ describe API::Members, api: true do
context "when authenticated as a #{type}" do
it 'returns 200' do
user = public_send(type)
+
get api("/#{source_type.pluralize}/#{source.id}/members", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
expect(json_response.size).to eq(2)
expect(json_response.map { |u| u['id'] }).to match_array [master.id, developer.id]
end
@@ -49,6 +52,8 @@ describe API::Members, api: true do
get api("/#{source_type.pluralize}/#{source.id}/members", developer)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
expect(json_response.size).to eq(2)
expect(json_response.map { |u| u['id'] }).to match_array [master.id, developer.id]
end
@@ -57,6 +62,8 @@ describe API::Members, api: true do
get api("/#{source_type.pluralize}/#{source.id}/members", developer), query: master.username
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
expect(json_response.count).to eq(1)
expect(json_response.first['username']).to eq(master.username)
end
@@ -145,11 +152,11 @@ describe API::Members, api: true do
end
end
- it "returns #{source_type == 'project' ? 201 : 409} if member already exists" do
+ it "returns 409 if member already exists" do
post api("/#{source_type.pluralize}/#{source.id}/members", master),
user_id: master.id, access_level: Member::MASTER
- expect(response).to have_http_status(source_type == 'project' ? 201 : 409)
+ expect(response).to have_http_status(409)
end
it 'returns 400 when user_id is not given' do
diff --git a/spec/requests/api/merge_request_diffs_spec.rb b/spec/requests/api/merge_request_diffs_spec.rb
index e1887138aab..1d02e827183 100644
--- a/spec/requests/api/merge_request_diffs_spec.rb
+++ b/spec/requests/api/merge_request_diffs_spec.rb
@@ -19,6 +19,8 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs', api: true do
merge_request_diff = merge_request.merge_request_diffs.first
expect(response.status).to eq 200
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
expect(json_response.size).to eq(merge_request.merge_request_diffs.size)
expect(json_response.first['id']).to eq(merge_request_diff.id)
expect(json_response.first['head_commit_sha']).to eq(merge_request_diff.head_commit_sha)
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index ff10e79e417..b87d0cd7de9 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -27,7 +27,9 @@ describe API::MergeRequests, api: true do
context "when authenticated" do
it "returns an array of all merge_requests" do
get api("/projects/#{project.id}/merge_requests", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
expect(json_response.last['title']).to eq(merge_request.title)
@@ -43,7 +45,9 @@ describe API::MergeRequests, api: true do
it "returns an array of all merge_requests" do
get api("/projects/#{project.id}/merge_requests?state", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
expect(json_response.last['title']).to eq(merge_request.title)
@@ -51,7 +55,9 @@ describe API::MergeRequests, api: true do
it "returns an array of open merge_requests" do
get api("/projects/#{project.id}/merge_requests?state=opened", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.last['title']).to eq(merge_request.title)
@@ -59,7 +65,9 @@ describe API::MergeRequests, api: true do
it "returns an array of closed merge_requests" do
get api("/projects/#{project.id}/merge_requests?state=closed", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['title']).to eq(merge_request_closed.title)
@@ -67,7 +75,9 @@ describe API::MergeRequests, api: true do
it "returns an array of merged merge_requests" do
get api("/projects/#{project.id}/merge_requests?state=merged", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['title']).to eq(merge_request_merged.title)
@@ -91,7 +101,9 @@ describe API::MergeRequests, api: true do
it "returns an array of merge_requests in ascending order" do
get api("/projects/#{project.id}/merge_requests?sort=asc", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
response_dates = json_response.map{ |merge_request| merge_request['created_at'] }
@@ -100,7 +112,9 @@ describe API::MergeRequests, api: true do
it "returns an array of merge_requests in descending order" do
get api("/projects/#{project.id}/merge_requests?sort=desc", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
response_dates = json_response.map{ |merge_request| merge_request['created_at'] }
@@ -109,7 +123,9 @@ describe API::MergeRequests, api: true do
it "returns an array of merge_requests ordered by updated_at" do
get api("/projects/#{project.id}/merge_requests?order_by=updated_at", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
response_dates = json_response.map{ |merge_request| merge_request['updated_at'] }
@@ -118,7 +134,9 @@ describe API::MergeRequests, api: true do
it "returns an array of merge_requests ordered by created_at" do
get api("/projects/#{project.id}/merge_requests?order_by=created_at&sort=asc", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
response_dates = json_response.map{ |merge_request| merge_request['created_at'] }
@@ -191,6 +209,8 @@ describe API::MergeRequests, api: true do
commit = merge_request.commits.first
expect(response.status).to eq 200
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
expect(json_response.size).to eq(merge_request.commits.size)
expect(json_response.first['id']).to eq(commit.id)
expect(json_response.first['title']).to eq(commit.title)
@@ -205,6 +225,7 @@ describe API::MergeRequests, api: true do
describe 'GET /projects/:id/merge_requests/:merge_request_id/changes' do
it 'returns the change information of the merge_request' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/changes", user)
+
expect(response.status).to eq 200
expect(json_response['changes'].size).to eq(merge_request.diffs.size)
end
@@ -229,7 +250,7 @@ describe API::MergeRequests, api: true do
expect(response).to have_http_status(201)
expect(json_response['title']).to eq('Test merge_request')
- expect(json_response['labels']).to eq(['label', 'label2'])
+ expect(json_response['labels']).to eq(%w(label label2))
expect(json_response['milestone']['id']).to eq(milestone.id)
expect(json_response['force_remove_source_branch']).to be_truthy
end
@@ -572,7 +593,9 @@ describe API::MergeRequests, api: true do
it "returns merge_request comments ordered by created_at" do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
expect(json_response.first['note']).to eq("a comment on a MR")
@@ -594,7 +617,9 @@ describe API::MergeRequests, api: true do
end
get api("/projects/#{project.id}/merge_requests/#{mr.id}/closes_issues", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(issue.id)
@@ -602,7 +627,9 @@ describe API::MergeRequests, api: true do
it 'returns an empty array when there are no issues to be closed' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/closes_issues", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
@@ -616,6 +643,7 @@ describe API::MergeRequests, api: true do
get api("/projects/#{jira_project.id}/merge_requests/#{merge_request.id}/closes_issues", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['title']).to eq(issue.title)
@@ -634,22 +662,22 @@ describe API::MergeRequests, api: true do
end
end
- describe 'POST :id/merge_requests/:merge_request_id/subscription' do
+ describe 'POST :id/merge_requests/:merge_request_id/subscribe' do
it 'subscribes to a merge request' do
- post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", admin)
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscribe", admin)
expect(response).to have_http_status(201)
expect(json_response['subscribed']).to eq(true)
end
it 'returns 304 if already subscribed' do
- post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", user)
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscribe", user)
expect(response).to have_http_status(304)
end
it 'returns 404 if the merge request is not found' do
- post api("/projects/#{project.id}/merge_requests/123/subscription", user)
+ post api("/projects/#{project.id}/merge_requests/123/subscribe", user)
expect(response).to have_http_status(404)
end
@@ -658,28 +686,28 @@ describe API::MergeRequests, api: true do
guest = create(:user)
project.team << [guest, :guest]
- post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", guest)
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscribe", guest)
expect(response).to have_http_status(403)
end
end
- describe 'DELETE :id/merge_requests/:merge_request_id/subscription' do
+ describe 'POST :id/merge_requests/:merge_request_id/unsubscribe' do
it 'unsubscribes from a merge request' do
- delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", user)
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/unsubscribe", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_http_status(201)
expect(json_response['subscribed']).to eq(false)
end
it 'returns 304 if not subscribed' do
- delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", admin)
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/unsubscribe", admin)
expect(response).to have_http_status(304)
end
it 'returns 404 if the merge request is not found' do
- post api("/projects/#{project.id}/merge_requests/123/subscription", user)
+ post api("/projects/#{project.id}/merge_requests/123/unsubscribe", user)
expect(response).to have_http_status(404)
end
@@ -688,7 +716,7 @@ describe API::MergeRequests, api: true do
guest = create(:user)
project.team << [guest, :guest]
- delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", guest)
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/unsubscribe", guest)
expect(response).to have_http_status(403)
end
diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb
index 8beef821d6c..418bf5a507c 100644
--- a/spec/requests/api/milestones_spec.rb
+++ b/spec/requests/api/milestones_spec.rb
@@ -14,6 +14,7 @@ describe API::Milestones, api: true do
get api("/projects/#{project.id}/milestones", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['title']).to eq(milestone.title)
end
@@ -28,6 +29,7 @@ describe API::Milestones, api: true do
get api("/projects/#{project.id}/milestones?state=active", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(milestone.id)
@@ -37,25 +39,18 @@ describe API::Milestones, api: true do
get api("/projects/#{project.id}/milestones?state=closed", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(closed_milestone.id)
end
- end
-
- describe 'GET /projects/:id/milestones/:milestone_id' do
- it 'returns a project milestone by id' do
- get api("/projects/#{project.id}/milestones/#{milestone.id}", user)
-
- expect(response).to have_http_status(200)
- expect(json_response['title']).to eq(milestone.title)
- expect(json_response['iid']).to eq(milestone.iid)
- end
it 'returns a project milestone by iid' do
get api("/projects/#{project.id}/milestones?iid=#{closed_milestone.iid}", user)
expect(response.status).to eq 200
+ expect(response).to include_pagination_headers
+ expect(json_response.size).to eq(1)
expect(json_response.size).to eq(1)
expect(json_response.first['title']).to eq closed_milestone.title
expect(json_response.first['id']).to eq closed_milestone.id
@@ -70,6 +65,26 @@ describe API::Milestones, api: true do
expect(json_response.first['id']).to eq milestone.id
end
+ it 'returns a project milestone by iid array' do
+ get api("/projects/#{project.id}/milestones", user), iid: [milestone.iid, closed_milestone.iid]
+
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response.size).to eq(2)
+ expect(json_response.first['title']).to eq milestone.title
+ expect(json_response.first['id']).to eq milestone.id
+ end
+ end
+
+ describe 'GET /projects/:id/milestones/:milestone_id' do
+ it 'returns a project milestone by id' do
+ get api("/projects/#{project.id}/milestones/#{milestone.id}", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['title']).to eq(milestone.title)
+ expect(json_response['iid']).to eq(milestone.iid)
+ end
+
it 'returns 401 error if user not authenticated' do
get api("/projects/#{project.id}/milestones/#{milestone.id}")
@@ -177,6 +192,7 @@ describe API::Milestones, api: true do
get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['milestone']['title']).to eq(milestone.title)
end
@@ -202,6 +218,7 @@ describe API::Milestones, api: true do
get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to eq(2)
expect(json_response.map { |issue| issue['id'] }).to include(issue.id, confidential_issue.id)
@@ -214,6 +231,7 @@ describe API::Milestones, api: true do
get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", member)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to eq(1)
expect(json_response.map { |issue| issue['id'] }).to include(issue.id)
@@ -223,10 +241,47 @@ describe API::Milestones, api: true do
get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", create(:user))
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to eq(1)
expect(json_response.map { |issue| issue['id'] }).to include(issue.id)
end
end
end
+
+ describe 'GET /projects/:id/milestones/:milestone_id/merge_requests' do
+ let(:merge_request) { create(:merge_request, source_project: project) }
+ before do
+ milestone.merge_requests << merge_request
+ end
+
+ it 'returns project merge_requests for a particular milestone' do
+ get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(1)
+ expect(json_response.first['title']).to eq(merge_request.title)
+ expect(json_response.first['milestone']['title']).to eq(milestone.title)
+ end
+
+ it 'returns a 404 error if milestone id not found' do
+ get api("/projects/#{project.id}/milestones/1234/merge_requests", user)
+
+ expect(response).to have_http_status(404)
+ end
+
+ it 'returns a 404 if the user has no access to the milestone' do
+ new_user = create :user
+ get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests", new_user)
+
+ expect(response).to have_http_status(404)
+ end
+
+ it 'returns a 401 error if user not authenticated' do
+ get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests")
+
+ expect(response).to have_http_status(401)
+ end
+ end
end
diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb
index c1edf384d5c..da8fa06d0af 100644
--- a/spec/requests/api/namespaces_spec.rb
+++ b/spec/requests/api/namespaces_spec.rb
@@ -5,7 +5,7 @@ describe API::Namespaces, api: true do
let(:admin) { create(:admin) }
let(:user) { create(:user) }
let!(:group1) { create(:group) }
- let!(:group2) { create(:group) }
+ let!(:group2) { create(:group, :nested) }
describe "GET /namespaces" do
context "when unauthenticated" do
@@ -18,35 +18,41 @@ describe API::Namespaces, api: true do
context "when authenticated as admin" do
it "admin: returns an array of all namespaces" do
get api("/namespaces", admin)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
-
expect(json_response.length).to eq(Namespace.count)
end
it "admin: returns an array of matched namespaces" do
- get api("/namespaces?search=#{group1.name}", admin)
+ get api("/namespaces?search=#{group2.name}", admin)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
-
expect(json_response.length).to eq(1)
+ expect(json_response.last['path']).to eq(group2.path)
+ expect(json_response.last['full_path']).to eq(group2.full_path)
end
end
context "when authenticated as a regular user" do
it "user: returns an array of namespaces" do
get api("/namespaces", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
-
expect(json_response.length).to eq(1)
end
it "admin: returns an array of matched namespaces" do
get api("/namespaces?search=#{user.username}", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
-
expect(json_response.length).to eq(1)
end
end
diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb
index 0353ebea9e5..3cca4468be7 100644
--- a/spec/requests/api/notes_spec.rb
+++ b/spec/requests/api/notes_spec.rb
@@ -32,15 +32,12 @@ describe API::Notes, api: true do
before { project.team << [user, :reporter] }
describe "GET /projects/:id/noteable/:noteable_id/notes" do
- it_behaves_like 'a paginated resources' do
- let(:request) { get api("/projects/#{project.id}/issues/#{issue.id}/notes", user) }
- end
-
context "when noteable is an Issue" do
it "returns an array of issue notes" do
get api("/projects/#{project.id}/issues/#{issue.id}/notes", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['body']).to eq(issue_note.note)
end
@@ -56,6 +53,7 @@ describe API::Notes, api: true do
get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response).to be_empty
end
@@ -75,6 +73,7 @@ describe API::Notes, api: true do
get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", private_user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['body']).to eq(cross_reference_note.note)
end
@@ -87,6 +86,7 @@ describe API::Notes, api: true do
get api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['body']).to eq(snippet_note.note)
end
@@ -109,6 +109,7 @@ describe API::Notes, api: true do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/notes", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['body']).to eq(merge_request_note.note)
end
diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb
index b7a0b5a9e13..98d004b572e 100644
--- a/spec/requests/api/pipelines_spec.rb
+++ b/spec/requests/api/pipelines_spec.rb
@@ -15,15 +15,12 @@ describe API::Pipelines, api: true do
before { project.team << [user, :master] }
describe 'GET /projects/:id/pipelines ' do
- it_behaves_like 'a paginated resources' do
- let(:request) { get api("/projects/#{project.id}/pipelines", user) }
- end
-
context 'authorized user' do
it 'returns project pipelines' do
get api("/projects/#{project.id}/pipelines", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['sha']).to match /\A\h{40}\z/
expect(json_response.first['id']).to eq pipeline.id
diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb
index f4973d71088..20c76bd2c05 100644
--- a/spec/requests/api/project_hooks_spec.rb
+++ b/spec/requests/api/project_hooks_spec.rb
@@ -25,6 +25,7 @@ describe API::ProjectHooks, 'ProjectHooks', api: true do
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
+ expect(response).to include_pagination_headers
expect(json_response.count).to eq(1)
expect(json_response.first['url']).to eq("http://example.com")
expect(json_response.first['issues_events']).to eq(true)
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
index eea76c7bb94..da9df56401b 100644
--- a/spec/requests/api/project_snippets_spec.rb
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -16,9 +16,11 @@ describe API::ProjectSnippets, api: true do
internal_snippet = create(:project_snippet, :internal, project: project)
private_snippet = create(:project_snippet, :private, project: project)
- get api("/projects/#{project.id}/snippets/", user)
+ get api("/projects/#{project.id}/snippets", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
expect(json_response.size).to eq(3)
expect(json_response.map{ |snippet| snippet['id']} ).to include(public_snippet.id, internal_snippet.id, private_snippet.id)
expect(json_response.last).to have_key('web_url')
@@ -28,7 +30,10 @@ describe API::ProjectSnippets, api: true do
create(:project_snippet, :private, project: project)
get api("/projects/#{project.id}/snippets/", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
expect(json_response.size).to eq(0)
end
end
@@ -73,43 +78,33 @@ describe API::ProjectSnippets, api: true do
allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
end
- context 'when the project is private' do
- let(:private_project) { create(:project_empty_repo, :private) }
-
- context 'when the snippet is public' do
- it 'creates the snippet' do
- expect { create_snippet(private_project, visibility_level: Snippet::PUBLIC) }.
- to change { Snippet.count }.by(1)
- end
+ context 'when the snippet is private' do
+ it 'creates the snippet' do
+ expect { create_snippet(project, visibility_level: Snippet::PRIVATE) }.
+ to change { Snippet.count }.by(1)
end
end
- context 'when the project is public' do
- context 'when the snippet is private' do
- it 'creates the snippet' do
- expect { create_snippet(project, visibility_level: Snippet::PRIVATE) }.
- to change { Snippet.count }.by(1)
- end
+ context 'when the snippet is public' do
+ it 'rejects the shippet' do
+ expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
+ not_to change { Snippet.count }
+
+ expect(response).to have_http_status(400)
+ expect(json_response['message']).to eq({ "error" => "Spam detected" })
end
- context 'when the snippet is public' do
- it 'rejects the shippet' do
- expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
- not_to change { Snippet.count }
- expect(response).to have_http_status(400)
- end
-
- it 'creates a spam log' do
- expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
- to change { SpamLog.count }.by(1)
- end
+ it 'creates a spam log' do
+ expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
+ to change { SpamLog.count }.by(1)
end
end
end
end
describe 'PUT /projects/:project_id/snippets/:id/' do
- let(:snippet) { create(:project_snippet, author: admin) }
+ let(:visibility_level) { Snippet::PUBLIC }
+ let(:snippet) { create(:project_snippet, author: admin, visibility_level: visibility_level) }
it 'updates snippet' do
new_content = 'New content'
@@ -133,6 +128,56 @@ describe API::ProjectSnippets, api: true do
expect(response).to have_http_status(400)
end
+
+ context 'when the snippet is spam' do
+ def update_snippet(snippet_params = {})
+ put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}", admin), snippet_params
+ end
+
+ before do
+ allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
+ end
+
+ context 'when the snippet is private' do
+ let(:visibility_level) { Snippet::PRIVATE }
+
+ it 'creates the snippet' do
+ expect { update_snippet(title: 'Foo') }.
+ to change { snippet.reload.title }.to('Foo')
+ end
+ end
+
+ context 'when the snippet is public' do
+ let(:visibility_level) { Snippet::PUBLIC }
+
+ it 'rejects the snippet' do
+ expect { update_snippet(title: 'Foo') }.
+ not_to change { snippet.reload.title }
+ end
+
+ it 'creates a spam log' do
+ expect { update_snippet(title: 'Foo') }.
+ to change { SpamLog.count }.by(1)
+ end
+ end
+
+ context 'when the private snippet is made public' do
+ let(:visibility_level) { Snippet::PRIVATE }
+
+ it 'rejects the snippet' do
+ expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }.
+ not_to change { snippet.reload.title }
+
+ expect(response).to have_http_status(400)
+ expect(json_response['message']).to eq({ "error" => "Spam detected" })
+ end
+
+ it 'creates a spam log' do
+ expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }.
+ to change { SpamLog.count }.by(1)
+ end
+ end
+ end
end
describe 'DELETE /projects/:project_id/snippets/:id/' do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 225e2e005df..5de4426f3bd 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -41,54 +41,92 @@ describe API::Projects, api: true do
end
describe 'GET /projects' do
- before { project }
+ shared_examples_for 'projects response' do
+ it 'returns an array of projects' do
+ get api('/projects', current_user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.map { |p| p['id'] }).to contain_exactly(*projects.map(&:id))
+ end
+ end
+
+ let!(:public_project) { create(:empty_project, :public, name: 'public_project') }
+ before do
+ project
+ project2
+ project3
+ project4
+ end
context 'when unauthenticated' do
- it 'returns authentication error' do
- get api('/projects')
- expect(response).to have_http_status(401)
+ it_behaves_like 'projects response' do
+ let(:current_user) { nil }
+ let(:projects) { [public_project] }
end
end
context 'when authenticated as regular user' do
- it 'returns an array of projects' do
- get api('/projects', user)
- expect(response).to have_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.first['name']).to eq(project.name)
- expect(json_response.first['owner']['username']).to eq(user.username)
+ it_behaves_like 'projects response' do
+ let(:current_user) { user }
+ let(:projects) { [public_project, project, project2, project3] }
end
it 'includes the project labels as the tag_list' do
get api('/projects', user)
+
expect(response.status).to eq 200
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first.keys).to include('tag_list')
end
it 'includes open_issues_count' do
get api('/projects', user)
+
expect(response.status).to eq 200
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first.keys).to include('open_issues_count')
end
- it 'does not include open_issues_count' do
+ it 'does not include open_issues_count if issues are disabled' do
project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
get api('/projects', user)
+
expect(response.status).to eq 200
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(json_response.first.keys).not_to include('open_issues_count')
+ expect(json_response.find { |hash| hash['id'] == project.id }.keys).not_to include('open_issues_count')
end
- context 'GET /projects?simple=true' do
+ it "does not include statistics by default" do
+ get api('/projects', user)
+
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.first).not_to include('statistics')
+ end
+
+ it "includes statistics if requested" do
+ get api('/projects', user), statistics: true
+
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.first).to include 'statistics'
+ end
+
+ context 'and with simple=true' do
it 'returns a simplified version of all the projects' do
- expected_keys = ["id", "http_url_to_repo", "web_url", "name", "name_with_namespace", "path", "path_with_namespace"]
+ expected_keys = %w(id http_url_to_repo web_url name name_with_namespace path path_with_namespace)
get api('/projects?simple=true', user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first.keys).to match_array expected_keys
end
@@ -97,7 +135,9 @@ describe API::Projects, api: true do
context 'and using search' do
it 'returns searched project' do
get api('/projects', user), { search: project.name }
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
end
@@ -106,196 +146,116 @@ describe API::Projects, api: true do
context 'and using the visibility filter' do
it 'filters based on private visibility param' do
get api('/projects', user), { visibility: 'private' }
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::PRIVATE).count)
+ expect(json_response.map { |p| p['id'] }).to contain_exactly(project.id, project2.id, project3.id)
end
it 'filters based on internal visibility param' do
+ project2.update_attribute(:visibility_level, Gitlab::VisibilityLevel::INTERNAL)
+
get api('/projects', user), { visibility: 'internal' }
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::INTERNAL).count)
+ expect(json_response.map { |p| p['id'] }).to contain_exactly(project2.id)
end
it 'filters based on public visibility param' do
get api('/projects', user), { visibility: 'public' }
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::PUBLIC).count)
+ expect(json_response.map { |p| p['id'] }).to contain_exactly(public_project.id)
end
end
context 'and using sorting' do
- before do
- project2
- project3
- end
-
it 'returns the correct order when sorted by id' do
get api('/projects', user), { order_by: 'id', sort: 'desc' }
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['id']).to eq(project3.id)
end
end
- end
- end
-
- describe 'GET /projects/all' do
- before { project }
-
- context 'when unauthenticated' do
- it 'returns authentication error' do
- get api('/projects/all')
- expect(response).to have_http_status(401)
- end
- end
- context 'when authenticated as regular user' do
- it 'returns authentication error' do
- get api('/projects/all', user)
- expect(response).to have_http_status(403)
- end
- end
+ context 'and with owned=true' do
+ it 'returns an array of projects the user owns' do
+ get api('/projects', user4), owned: true
- context 'when authenticated as admin' do
- it 'returns an array of all projects' do
- get api('/projects/all', admin)
- expect(response).to have_http_status(200)
- expect(json_response).to be_an Array
-
- expect(json_response).to satisfy do |response|
- response.one? do |entry|
- entry.has_key?('permissions') &&
- entry['name'] == project.name &&
- entry['owner']['username'] == user.username
- end
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.first['name']).to eq(project4.name)
+ expect(json_response.first['owner']['username']).to eq(user4.username)
end
end
- it "does not include statistics by default" do
- get api('/projects/all', admin)
-
- expect(response).to have_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.first).not_to include('statistics')
- end
-
- it "includes statistics if requested" do
- get api('/projects/all', admin), statistics: true
-
- expect(response).to have_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.first).to include 'statistics'
- end
- end
- end
-
- describe 'GET /projects/owned' do
- before do
- project3
- project4
- end
+ context 'and with starred=true' do
+ let(:public_project) { create(:empty_project, :public) }
- context 'when unauthenticated' do
- it 'returns authentication error' do
- get api('/projects/owned')
- expect(response).to have_http_status(401)
- end
- end
-
- context 'when authenticated as project owner' do
- it 'returns an array of projects the user owns' do
- get api('/projects/owned', user4)
- expect(response).to have_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.first['name']).to eq(project4.name)
- expect(json_response.first['owner']['username']).to eq(user4.username)
- end
-
- it "does not include statistics by default" do
- get api('/projects/owned', user4)
-
- expect(response).to have_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.first).not_to include('statistics')
- end
-
- it "includes statistics if requested" do
- attributes = {
- commit_count: 23,
- storage_size: 702,
- repository_size: 123,
- lfs_objects_size: 234,
- build_artifacts_size: 345,
- }
-
- project4.statistics.update!(attributes)
+ before do
+ project_member2
+ user3.update_attributes(starred_projects: [project, project2, project3, public_project])
+ end
- get api('/projects/owned', user4), statistics: true
+ it 'returns the starred projects viewable by the user' do
+ get api('/projects', user3), starred: true
- expect(response).to have_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.first['statistics']).to eq attributes.stringify_keys
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.map { |project| project['id'] }).to contain_exactly(project.id, public_project.id)
+ end
end
- end
- end
- describe 'GET /projects/visible' do
- shared_examples_for 'visible projects response' do
- it 'returns the visible projects' do
- get api('/projects/visible', current_user)
+ context 'and with all query parameters' do
+ # | | project5 | project6 | project7 | project8 | project9 |
+ # |---------+----------+----------+----------+----------+----------|
+ # | search | x | | x | x | x |
+ # | starred | x | x | | x | x |
+ # | public | x | x | x | | x |
+ # | owned | x | x | x | x | |
+ let!(:project5) { create(:empty_project, :public, path: 'gitlab5', namespace: user.namespace) }
+ let!(:project6) { create(:empty_project, :public, path: 'project6', namespace: user.namespace) }
+ let!(:project7) { create(:empty_project, :public, path: 'gitlab7', namespace: user.namespace) }
+ let!(:project8) { create(:empty_project, path: 'gitlab8', namespace: user.namespace) }
+ let!(:project9) { create(:empty_project, :public, path: 'gitlab9') }
- expect(response).to have_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.map { |p| p['id'] }).to contain_exactly(*projects.map(&:id))
- end
- end
-
- let!(:public_project) { create(:empty_project, :public) }
- before do
- project
- project2
- project3
- project4
- end
+ before do
+ user.update_attributes(starred_projects: [project5, project6, project8, project9])
+ end
- context 'when unauthenticated' do
- it_behaves_like 'visible projects response' do
- let(:current_user) { nil }
- let(:projects) { [public_project] }
- end
- end
+ it 'returns only projects that satify all query parameters' do
+ get api('/projects', user), { visibility: 'public', owned: true, starred: true, search: 'gitlab' }
- context 'when authenticated' do
- it_behaves_like 'visible projects response' do
- let(:current_user) { user }
- let(:projects) { [public_project, project, project2, project3] }
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(1)
+ expect(json_response.first['id']).to eq(project5.id)
+ end
end
end
context 'when authenticated as a different user' do
- it_behaves_like 'visible projects response' do
+ it_behaves_like 'projects response' do
let(:current_user) { user2 }
let(:projects) { [public_project] }
end
end
- end
-
- describe 'GET /projects/starred' do
- let(:public_project) { create(:empty_project, :public) }
- before do
- project_member2
- user3.update_attributes(starred_projects: [project, project2, project3, public_project])
- end
-
- it 'returns the starred projects viewable by the user' do
- get api('/projects/starred', user3)
- expect(response).to have_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.map { |project| project['id'] }).to contain_exactly(project.id, public_project.id)
+ context 'when authenticated as admin' do
+ it_behaves_like 'projects response' do
+ let(:current_user) { admin }
+ let(:projects) { Project.all }
+ end
end
end
@@ -359,13 +319,6 @@ describe API::Projects, api: true do
expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC)
end
- it 'sets a project as public using :public' do
- project = attributes_for(:project, { public: true })
- post api('/projects', user), project
- expect(json_response['public']).to be_truthy
- expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC)
- end
-
it 'sets a project as internal' do
project = attributes_for(:project, :internal)
post api('/projects', user), project
@@ -373,13 +326,6 @@ describe API::Projects, api: true do
expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL)
end
- it 'sets a project as internal overriding :public' do
- project = attributes_for(:project, :internal, { public: true })
- post api('/projects', user), project
- expect(json_response['public']).to be_falsey
- expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL)
- end
-
it 'sets a project as private' do
project = attributes_for(:project, :private)
post api('/projects', user), project
@@ -387,13 +333,6 @@ describe API::Projects, api: true do
expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE)
end
- it 'sets a project as private using :public' do
- project = attributes_for(:project, { public: false })
- post api('/projects', user), project
- expect(json_response['public']).to be_falsey
- expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE)
- end
-
it 'sets a project as allowing merge even if build fails' do
project = attributes_for(:project, { only_allow_merge_if_build_succeeds: false })
post api('/projects', user), project
@@ -431,13 +370,14 @@ describe API::Projects, api: true do
end
context 'when a visibility level is restricted' do
+ let(:project_param) { attributes_for(:project, :public) }
+
before do
- @project = attributes_for(:project, { public: true })
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
end
it 'does not allow a non-admin to use a restricted visibility level' do
- post api('/projects', user), @project
+ post api('/projects', user), project_param
expect(response).to have_http_status(400)
expect(json_response['message']['visibility_level'].first).to(
@@ -446,7 +386,8 @@ describe API::Projects, api: true do
end
it 'allows an admin to override restricted visibility settings' do
- post api('/projects', admin), @project
+ post api('/projects', admin), project_param
+
expect(json_response['public']).to be_truthy
expect(json_response['visibility_level']).to(
eq(Gitlab::VisibilityLevel::PUBLIC)
@@ -499,15 +440,6 @@ describe API::Projects, api: true do
expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC)
end
- it 'sets a project as public using :public' do
- project = attributes_for(:project, { public: true })
- post api("/projects/user/#{user.id}", admin), project
-
- expect(response).to have_http_status(201)
- expect(json_response['public']).to be_truthy
- expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC)
- end
-
it 'sets a project as internal' do
project = attributes_for(:project, :internal)
post api("/projects/user/#{user.id}", admin), project
@@ -517,14 +449,6 @@ describe API::Projects, api: true do
expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL)
end
- it 'sets a project as internal overriding :public' do
- project = attributes_for(:project, :internal, { public: true })
- post api("/projects/user/#{user.id}", admin), project
- expect(response).to have_http_status(201)
- expect(json_response['public']).to be_falsey
- expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL)
- end
-
it 'sets a project as private' do
project = attributes_for(:project, :private)
post api("/projects/user/#{user.id}", admin), project
@@ -532,13 +456,6 @@ describe API::Projects, api: true do
expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE)
end
- it 'sets a project as private using :public' do
- project = attributes_for(:project, { public: false })
- post api("/projects/user/#{user.id}", admin), project
- expect(json_response['public']).to be_falsey
- expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE)
- end
-
it 'sets a project as allowing merge even if build fails' do
project = attributes_for(:project, { only_allow_merge_if_build_succeeds: false })
post api("/projects/user/#{user.id}", admin), project
@@ -682,6 +599,7 @@ describe API::Projects, api: true do
'name' => user.namespace.name,
'path' => user.namespace.path,
'kind' => user.namespace.kind,
+ 'full_path' => user.namespace.full_path,
})
end
@@ -740,9 +658,10 @@ describe API::Projects, api: true do
get api("/projects/#{project.id}/events", current_user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
first_event = json_response.first
-
expect(first_event['action_name']).to eq('commented on')
expect(first_event['note']['body']).to eq('What an awesome day!')
@@ -795,11 +714,11 @@ describe API::Projects, api: true do
get api("/projects/#{project.id}/users", current_user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to eq(1)
first_user = json_response.first
-
expect(first_user['username']).to eq(member.username)
expect(first_user['name']).to eq(member.name)
expect(first_user.keys).to contain_exactly(*%w[name username id state avatar_url web_url])
@@ -842,7 +761,9 @@ describe API::Projects, api: true do
it 'returns an array of project snippets' do
get api("/projects/#{project.id}/snippets", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['title']).to eq(snippet.title)
end
@@ -865,7 +786,7 @@ describe API::Projects, api: true do
it 'creates a new project snippet' do
post api("/projects/#{project.id}/snippets", user),
title: 'api test', file_name: 'sample.rb', code: 'test',
- visibility_level: '0'
+ visibility_level: Gitlab::VisibilityLevel::PRIVATE
expect(response).to have_http_status(201)
expect(json_response['title']).to eq('api test')
end
@@ -1114,7 +1035,7 @@ describe API::Projects, api: true do
end
it 'updates visibility_level' do
- project_param = { visibility_level: 20 }
+ project_param = { visibility_level: Gitlab::VisibilityLevel::PUBLIC }
put api("/projects/#{project3.id}", user), project_param
expect(response).to have_http_status(200)
project_param.each_pair do |k, v|
@@ -1124,7 +1045,7 @@ describe API::Projects, api: true do
it 'updates visibility_level from public to private' do
project3.update_attributes({ visibility_level: Gitlab::VisibilityLevel::PUBLIC })
- project_param = { public: false }
+ project_param = { visibility_level: Gitlab::VisibilityLevel::PRIVATE }
put api("/projects/#{project3.id}", user), project_param
expect(response).to have_http_status(200)
project_param.each_pair do |k, v|
@@ -1197,7 +1118,7 @@ describe API::Projects, api: true do
end
it 'does not update visibility_level' do
- project_param = { visibility_level: 20 }
+ project_param = { visibility_level: Gitlab::VisibilityLevel::PUBLIC }
put api("/projects/#{project3.id}", user4), project_param
expect(response).to have_http_status(403)
end
@@ -1314,7 +1235,7 @@ describe API::Projects, api: true do
end
end
- describe 'DELETE /projects/:id/star' do
+ describe 'POST /projects/:id/unstar' do
context 'on a starred project' do
before do
user.toggle_star(project)
@@ -1322,16 +1243,16 @@ describe API::Projects, api: true do
end
it 'unstars the project' do
- expect { delete api("/projects/#{project.id}/star", user) }.to change { project.reload.star_count }.by(-1)
+ expect { post api("/projects/#{project.id}/unstar", user) }.to change { project.reload.star_count }.by(-1)
- expect(response).to have_http_status(200)
+ expect(response).to have_http_status(201)
expect(json_response['star_count']).to eq(0)
end
end
context 'on an unstarred project' do
it 'does not modify the star count' do
- expect { delete api("/projects/#{project.id}/star", user) }.not_to change { project.reload.star_count }
+ expect { post api("/projects/#{project.id}/unstar", user) }.not_to change { project.reload.star_count }
expect(response).to have_http_status(304)
end
@@ -1342,7 +1263,9 @@ describe API::Projects, api: true do
context 'when authenticated as user' do
it 'removes project' do
delete api("/projects/#{project.id}", user)
- expect(response).to have_http_status(200)
+
+ expect(response).to have_http_status(202)
+ expect(json_response['message']).to eql('202 Accepted')
end
it 'does not remove a project if not an owner' do
@@ -1366,7 +1289,9 @@ describe API::Projects, api: true do
context 'when authenticated as admin' do
it 'removes any existing project' do
delete api("/projects/#{project.id}", admin)
- expect(response).to have_http_status(200)
+
+ expect(response).to have_http_status(202)
+ expect(json_response['message']).to eql('202 Accepted')
end
it 'does not remove a non existing project' do
@@ -1375,4 +1300,179 @@ describe API::Projects, api: true do
end
end
end
+
+ describe 'POST /projects/:id/fork' do
+ let(:project) do
+ create(:project, :repository, creator: user, namespace: user.namespace)
+ end
+ let(:group) { create(:group) }
+ let(:group2) do
+ group = create(:group, name: 'group2_name')
+ group.add_owner(user2)
+ group
+ end
+
+ before do
+ project.add_reporter(user2)
+ end
+
+ context 'when authenticated' do
+ it 'forks if user has sufficient access to project' do
+ post api("/projects/#{project.id}/fork", user2)
+
+ expect(response).to have_http_status(201)
+ expect(json_response['name']).to eq(project.name)
+ expect(json_response['path']).to eq(project.path)
+ expect(json_response['owner']['id']).to eq(user2.id)
+ expect(json_response['namespace']['id']).to eq(user2.namespace.id)
+ expect(json_response['forked_from_project']['id']).to eq(project.id)
+ end
+
+ it 'forks if user is admin' do
+ post api("/projects/#{project.id}/fork", admin)
+
+ expect(response).to have_http_status(201)
+ expect(json_response['name']).to eq(project.name)
+ expect(json_response['path']).to eq(project.path)
+ expect(json_response['owner']['id']).to eq(admin.id)
+ expect(json_response['namespace']['id']).to eq(admin.namespace.id)
+ expect(json_response['forked_from_project']['id']).to eq(project.id)
+ end
+
+ it 'fails on missing project access for the project to fork' do
+ new_user = create(:user)
+ post api("/projects/#{project.id}/fork", new_user)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 Project Not Found')
+ end
+
+ it 'fails if forked project exists in the user namespace' do
+ post api("/projects/#{project.id}/fork", user)
+
+ expect(response).to have_http_status(409)
+ expect(json_response['message']['name']).to eq(['has already been taken'])
+ expect(json_response['message']['path']).to eq(['has already been taken'])
+ end
+
+ it 'fails if project to fork from does not exist' do
+ post api('/projects/424242/fork', user)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 Project Not Found')
+ end
+
+ it 'forks with explicit own user namespace id' do
+ post api("/projects/#{project.id}/fork", user2), namespace: user2.namespace.id
+
+ expect(response).to have_http_status(201)
+ expect(json_response['owner']['id']).to eq(user2.id)
+ end
+
+ it 'forks with explicit own user name as namespace' do
+ post api("/projects/#{project.id}/fork", user2), namespace: user2.username
+
+ expect(response).to have_http_status(201)
+ expect(json_response['owner']['id']).to eq(user2.id)
+ end
+
+ it 'forks to another user when admin' do
+ post api("/projects/#{project.id}/fork", admin), namespace: user2.username
+
+ expect(response).to have_http_status(201)
+ expect(json_response['owner']['id']).to eq(user2.id)
+ end
+
+ it 'fails if trying to fork to another user when not admin' do
+ post api("/projects/#{project.id}/fork", user2), namespace: admin.namespace.id
+
+ expect(response).to have_http_status(404)
+ end
+
+ it 'fails if trying to fork to non-existent namespace' do
+ post api("/projects/#{project.id}/fork", user2), namespace: 42424242
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 Target Namespace Not Found')
+ end
+
+ it 'forks to owned group' do
+ post api("/projects/#{project.id}/fork", user2), namespace: group2.name
+
+ expect(response).to have_http_status(201)
+ expect(json_response['namespace']['name']).to eq(group2.name)
+ end
+
+ it 'fails to fork to not owned group' do
+ post api("/projects/#{project.id}/fork", user2), namespace: group.name
+
+ expect(response).to have_http_status(404)
+ end
+
+ it 'forks to not owned group when admin' do
+ post api("/projects/#{project.id}/fork", admin), namespace: group.name
+
+ expect(response).to have_http_status(201)
+ expect(json_response['namespace']['name']).to eq(group.name)
+ end
+ end
+
+ context 'when unauthenticated' do
+ it 'returns authentication error' do
+ post api("/projects/#{project.id}/fork")
+
+ expect(response).to have_http_status(401)
+ expect(json_response['message']).to eq('401 Unauthorized')
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/housekeeping' do
+ let(:housekeeping) { Projects::HousekeepingService.new(project) }
+
+ before do
+ allow(Projects::HousekeepingService).to receive(:new).with(project).and_return(housekeeping)
+ end
+
+ context 'when authenticated as owner' do
+ it 'starts the housekeeping process' do
+ expect(housekeeping).to receive(:execute).once
+
+ post api("/projects/#{project.id}/housekeeping", user)
+
+ expect(response).to have_http_status(201)
+ end
+
+ context 'when housekeeping lease is taken' do
+ it 'returns conflict' do
+ expect(housekeeping).to receive(:execute).once.and_raise(Projects::HousekeepingService::LeaseTaken)
+
+ post api("/projects/#{project.id}/housekeeping", user)
+
+ expect(response).to have_http_status(409)
+ expect(json_response['message']).to match(/Somebody already triggered housekeeping for this project/)
+ end
+ end
+ end
+
+ context 'when authenticated as developer' do
+ before do
+ project_member2
+ end
+
+ it 'returns forbidden error' do
+ post api("/projects/#{project.id}/housekeeping", user3)
+
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ context 'when unauthenticated' do
+ it 'returns authentication error' do
+ post api("/projects/#{project.id}/housekeeping")
+
+ expect(response).to have_http_status(401)
+ end
+ end
+ end
end
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index c61208e395c..7652606a491 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -19,10 +19,10 @@ describe API::Repositories, api: true do
get api(route, current_user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
first_commit = json_response.first
-
- expect(json_response).to be_an Array
expect(first_commit['name']).to eq('bar')
expect(first_commit['type']).to eq('tree')
expect(first_commit['mode']).to eq('040000')
@@ -49,6 +49,7 @@ describe API::Repositories, api: true do
expect(response.status).to eq(200)
expect(json_response).to be_an Array
+ expect(response).to include_pagination_headers
expect(json_response[4]['name']).to eq('html')
expect(json_response[4]['path']).to eq('files/html')
expect(json_response[4]['type']).to eq('tree')
@@ -380,10 +381,10 @@ describe API::Repositories, api: true do
get api(route, current_user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
first_contributor = json_response.first
-
expect(first_contributor['email']).to eq('tiagonbotelho@hotmail.com')
expect(first_contributor['name']).to eq('tiagonbotelho')
expect(first_contributor['commits']).to eq(1)
diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb
index f2d81a28cb8..103d6755888 100644
--- a/spec/requests/api/runners_spec.rb
+++ b/spec/requests/api/runners_spec.rb
@@ -37,18 +37,20 @@ describe API::Runners, api: true do
context 'authorized user' do
it 'returns user available runners' do
get api('/runners', user)
- shared = json_response.any?{ |r| r['is_shared'] }
+ shared = json_response.any?{ |r| r['is_shared'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(shared).to be_falsey
end
it 'filters runners by scope' do
get api('/runners?scope=active', user)
- shared = json_response.any?{ |r| r['is_shared'] }
+ shared = json_response.any?{ |r| r['is_shared'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(shared).to be_falsey
end
@@ -73,9 +75,10 @@ describe API::Runners, api: true do
context 'with admin privileges' do
it 'returns all runners' do
get api('/runners/all', admin)
- shared = json_response.any?{ |r| r['is_shared'] }
+ shared = json_response.any?{ |r| r['is_shared'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(shared).to be_truthy
end
@@ -91,9 +94,10 @@ describe API::Runners, api: true do
it 'filters runners by scope' do
get api('/runners/all?scope=specific', admin)
- shared = json_response.any?{ |r| r['is_shared'] }
+ shared = json_response.any?{ |r| r['is_shared'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(shared).to be_falsey
end
@@ -183,6 +187,7 @@ describe API::Runners, api: true do
it 'updates runner' do
description = shared_runner.description
active = shared_runner.active
+ runner_queue_value = shared_runner.ensure_runner_queue_value
update_runner(shared_runner.id, admin, description: "#{description}_updated",
active: !active,
@@ -197,18 +202,24 @@ describe API::Runners, api: true do
expect(shared_runner.tag_list).to include('ruby2.1', 'pgsql', 'mysql')
expect(shared_runner.run_untagged?).to be(false)
expect(shared_runner.locked?).to be(true)
+ expect(shared_runner.ensure_runner_queue_value)
+ .not_to eq(runner_queue_value)
end
end
context 'when runner is not shared' do
it 'updates runner' do
description = specific_runner.description
+ runner_queue_value = specific_runner.ensure_runner_queue_value
+
update_runner(specific_runner.id, admin, description: 'test')
specific_runner.reload
expect(response).to have_http_status(200)
expect(specific_runner.description).to eq('test')
expect(specific_runner.description).not_to eq(description)
+ expect(specific_runner.ensure_runner_queue_value)
+ .not_to eq(runner_queue_value)
end
end
@@ -335,9 +346,10 @@ describe API::Runners, api: true do
context 'authorized user with master privileges' do
it "returns project's runners" do
get api("/projects/#{project.id}/runners", user)
- shared = json_response.any?{ |r| r['is_shared'] }
+ shared = json_response.any?{ |r| r['is_shared'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(shared).to be_truthy
end
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index 91e3c333a02..411905edb49 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -30,8 +30,14 @@ describe API::Settings, 'Settings', api: true do
it "updates application settings" do
put api("/application/settings", admin),
- default_projects_limit: 3, signin_enabled: false, repository_storage: 'custom', koding_enabled: true, koding_url: 'http://koding.example.com',
- plantuml_enabled: true, plantuml_url: 'http://plantuml.example.com'
+ default_projects_limit: 3,
+ signin_enabled: false,
+ repository_storage: 'custom',
+ koding_enabled: true,
+ koding_url: 'http://koding.example.com',
+ plantuml_enabled: true,
+ plantuml_url: 'http://plantuml.example.com',
+ default_artifacts_expire_in: '2 days'
expect(response).to have_http_status(200)
expect(json_response['default_projects_limit']).to eq(3)
expect(json_response['signin_enabled']).to be_falsey
@@ -41,6 +47,7 @@ describe API::Settings, 'Settings', api: true do
expect(json_response['koding_url']).to eq('http://koding.example.com')
expect(json_response['plantuml_enabled']).to be_truthy
expect(json_response['plantuml_url']).to eq('http://plantuml.example.com')
+ expect(json_response['default_artifacts_expire_in']).to eq('2 days')
end
end
diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb
index 6b9a739b439..41def7cd1d4 100644
--- a/spec/requests/api/snippets_spec.rb
+++ b/spec/requests/api/snippets_spec.rb
@@ -13,6 +13,8 @@ describe API::Snippets, api: true do
get api("/snippets/", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly(
public_snippet.id,
internal_snippet.id,
@@ -25,7 +27,10 @@ describe API::Snippets, api: true do
create(:personal_snippet, :private)
get api("/snippets/", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
expect(json_response.size).to eq(0)
end
end
@@ -43,6 +48,8 @@ describe API::Snippets, api: true do
get api("/snippets/public", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly(
public_snippet.id,
public_snippet_other.id)
@@ -122,7 +129,9 @@ describe API::Snippets, api: true do
it 'rejects the shippet' do
expect { create_snippet(visibility_level: Snippet::PUBLIC) }.
not_to change { Snippet.count }
+
expect(response).to have_http_status(400)
+ expect(json_response['message']).to eq({ "error" => "Spam detected" })
end
it 'creates a spam log' do
@@ -134,16 +143,20 @@ describe API::Snippets, api: true do
end
describe 'PUT /snippets/:id' do
+ let(:visibility_level) { Snippet::PUBLIC }
let(:other_user) { create(:user) }
- let(:public_snippet) { create(:personal_snippet, :public, author: user) }
+ let(:snippet) do
+ create(:personal_snippet, author: user, visibility_level: visibility_level)
+ end
+
it 'updates snippet' do
new_content = 'New content'
- put api("/snippets/#{public_snippet.id}", user), content: new_content
+ put api("/snippets/#{snippet.id}", user), content: new_content
expect(response).to have_http_status(200)
- public_snippet.reload
- expect(public_snippet.content).to eq(new_content)
+ snippet.reload
+ expect(snippet.content).to eq(new_content)
end
it 'returns 404 for invalid snippet id' do
@@ -154,7 +167,7 @@ describe API::Snippets, api: true do
end
it "returns 404 for another user's snippet" do
- put api("/snippets/#{public_snippet.id}", other_user), title: 'fubar'
+ put api("/snippets/#{snippet.id}", other_user), title: 'fubar'
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 Snippet Not Found')
@@ -165,6 +178,56 @@ describe API::Snippets, api: true do
expect(response).to have_http_status(400)
end
+
+ context 'when the snippet is spam' do
+ def update_snippet(snippet_params = {})
+ put api("/snippets/#{snippet.id}", user), snippet_params
+ end
+
+ before do
+ allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
+ end
+
+ context 'when the snippet is private' do
+ let(:visibility_level) { Snippet::PRIVATE }
+
+ it 'updates the snippet' do
+ expect { update_snippet(title: 'Foo') }.
+ to change { snippet.reload.title }.to('Foo')
+ end
+ end
+
+ context 'when the snippet is public' do
+ let(:visibility_level) { Snippet::PUBLIC }
+
+ it 'rejects the shippet' do
+ expect { update_snippet(title: 'Foo') }.
+ not_to change { snippet.reload.title }
+
+ expect(response).to have_http_status(400)
+ expect(json_response['message']).to eq({ "error" => "Spam detected" })
+ end
+
+ it 'creates a spam log' do
+ expect { update_snippet(title: 'Foo') }.
+ to change { SpamLog.count }.by(1)
+ end
+ end
+
+ context 'when a private snippet is made public' do
+ let(:visibility_level) { Snippet::PRIVATE }
+
+ it 'rejects the snippet' do
+ expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }.
+ not_to change { snippet.reload.title }
+ end
+
+ it 'creates a spam log' do
+ expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }.
+ to change { SpamLog.count }.by(1)
+ end
+ end
+ end
end
describe 'DELETE /snippets/:id' do
diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb
index b3e5afdadb1..b59da632c00 100644
--- a/spec/requests/api/system_hooks_spec.rb
+++ b/spec/requests/api/system_hooks_spec.rb
@@ -31,6 +31,7 @@ describe API::SystemHooks, api: true do
get api("/hooks", admin)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['url']).to eq(hook.url)
expect(json_response.first['push_events']).to be true
diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb
index 898d2b27e5c..8a4f078182f 100644
--- a/spec/requests/api/tags_spec.rb
+++ b/spec/requests/api/tags_spec.rb
@@ -20,10 +20,9 @@ describe API::Tags, api: true do
get api("/projects/#{project.id}/repository/tags", current_user)
expect(response).to have_http_status(200)
-
- first_tag = json_response.first
-
- expect(first_tag['name']).to eq(tag_name)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.first['name']).to eq(tag_name)
end
end
@@ -43,7 +42,9 @@ describe API::Tags, api: true do
context 'without releases' do
it "returns an array of project tags" do
get api("/projects/#{project.id}/repository/tags", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['name']).to eq(tag_name)
end
@@ -59,6 +60,7 @@ describe API::Tags, api: true do
get api("/projects/#{project.id}/repository/tags", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['name']).to eq(tag_name)
expect(json_response.first['message']).to eq('Version 1.1.0')
diff --git a/spec/requests/api/templates_spec.rb b/spec/requests/api/templates_spec.rb
index d32ba60fc4c..2c83e119065 100644
--- a/spec/requests/api/templates_spec.rb
+++ b/spec/requests/api/templates_spec.rb
@@ -3,51 +3,53 @@ require 'spec_helper'
describe API::Templates, api: true do
include ApiHelpers
- shared_examples_for 'the Template Entity' do |path|
- before { get api(path) }
+ context 'the Template Entity' do
+ before { get api('/templates/gitignores/Ruby') }
it { expect(json_response['name']).to eq('Ruby') }
it { expect(json_response['content']).to include('*.gem') }
end
-
- shared_examples_for 'the TemplateList Entity' do |path|
- before { get api(path) }
+
+ context 'the TemplateList Entity' do
+ before { get api('/templates/gitignores') }
it { expect(json_response.first['name']).not_to be_nil }
it { expect(json_response.first['content']).to be_nil }
end
- shared_examples_for 'requesting gitignores' do |path|
+ context 'requesting gitignores' do
it 'returns a list of available gitignore templates' do
- get api(path)
+ get api('/templates/gitignores')
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to be > 15
end
end
- shared_examples_for 'requesting gitlab-ci-ymls' do |path|
+ context 'requesting gitlab-ci-ymls' do
it 'returns a list of available gitlab_ci_ymls' do
- get api(path)
+ get api('/templates/gitlab_ci_ymls')
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['name']).not_to be_nil
end
end
- shared_examples_for 'requesting gitlab-ci-yml for Ruby' do |path|
+ context 'requesting gitlab-ci-yml for Ruby' do
it 'adds a disclaimer on the top' do
- get api(path)
+ get api('/templates/gitlab_ci_ymls/Ruby')
expect(response).to have_http_status(200)
expect(json_response['content']).to start_with("# This file is a template,")
end
end
- shared_examples_for 'the License Template Entity' do |path|
- before { get api(path) }
+ context 'the License Template Entity' do
+ before { get api('/templates/licenses/mit') }
it 'returns a license template' do
expect(json_response['key']).to eq('mit')
@@ -56,30 +58,32 @@ describe API::Templates, api: true do
expect(json_response['popular']).to be true
expect(json_response['html_url']).to eq('http://choosealicense.com/licenses/mit/')
expect(json_response['source_url']).to eq('https://opensource.org/licenses/MIT')
- expect(json_response['description']).to include('A permissive license that is short and to the point.')
+ expect(json_response['description']).to include('A short and simple permissive license with conditions')
expect(json_response['conditions']).to eq(%w[include-copyright])
expect(json_response['permissions']).to eq(%w[commercial-use modifications distribution private-use])
expect(json_response['limitations']).to eq(%w[no-liability])
- expect(json_response['content']).to include('The MIT License (MIT)')
+ expect(json_response['content']).to include('MIT License')
end
end
- shared_examples_for 'GET licenses' do |path|
+ context 'GET templates/licenses' do
it 'returns a list of available license templates' do
- get api(path)
+ get api('/templates/licenses')
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(json_response.size).to eq(15)
+ expect(json_response.size).to eq(12)
expect(json_response.map { |l| l['key'] }).to include('agpl-3.0')
end
describe 'the popular parameter' do
context 'with popular=1' do
it 'returns a list of available popular license templates' do
- get api("#{path}?popular=1")
+ get api('/templates/licenses?popular=1')
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to eq(3)
expect(json_response.map { |l| l['key'] }).to include('apache-2.0')
@@ -88,17 +92,17 @@ describe API::Templates, api: true do
end
end
- shared_examples_for 'GET licenses/:name' do |path|
+ context 'GET templates/licenses/:name' do
context 'with :project and :fullname given' do
before do
- get api("#{path}/#{license_type}?project=My+Awesome+Project&fullname=Anton+#{license_type.upcase}")
+ get api("/templates/licenses/#{license_type}?project=My+Awesome+Project&fullname=Anton+#{license_type.upcase}")
end
context 'for the mit license' do
let(:license_type) { 'mit' }
it 'returns the license text' do
- expect(json_response['content']).to include('The MIT License (MIT)')
+ expect(json_response['content']).to include('MIT License')
end
it 'replaces placeholder values' do
@@ -178,26 +182,4 @@ describe API::Templates, api: true do
end
end
end
-
- describe 'with /templates namespace' do
- it_behaves_like 'the Template Entity', '/templates/gitignores/Ruby'
- it_behaves_like 'the TemplateList Entity', '/templates/gitignores'
- it_behaves_like 'requesting gitignores', '/templates/gitignores'
- it_behaves_like 'requesting gitlab-ci-ymls', '/templates/gitlab_ci_ymls'
- it_behaves_like 'requesting gitlab-ci-yml for Ruby', '/templates/gitlab_ci_ymls/Ruby'
- it_behaves_like 'the License Template Entity', '/templates/licenses/mit'
- it_behaves_like 'GET licenses', '/templates/licenses'
- it_behaves_like 'GET licenses/:name', '/templates/licenses'
- end
-
- describe 'without /templates namespace' do
- it_behaves_like 'the Template Entity', '/gitignores/Ruby'
- it_behaves_like 'the TemplateList Entity', '/gitignores'
- it_behaves_like 'requesting gitignores', '/gitignores'
- it_behaves_like 'requesting gitlab-ci-ymls', '/gitlab_ci_ymls'
- it_behaves_like 'requesting gitlab-ci-yml for Ruby', '/gitlab_ci_ymls/Ruby'
- it_behaves_like 'the License Template Entity', '/licenses/mit'
- it_behaves_like 'GET licenses', '/licenses'
- it_behaves_like 'GET licenses/:name', '/licenses'
- end
end
diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb
index 56dc017ce54..f35e963a14b 100644
--- a/spec/requests/api/todos_spec.rb
+++ b/spec/requests/api/todos_spec.rb
@@ -33,6 +33,7 @@ describe API::Todos, api: true do
get api('/todos', john_doe)
expect(response.status).to eq(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
expect(json_response[0]['id']).to eq(pending_3.id)
@@ -52,6 +53,7 @@ describe API::Todos, api: true do
get api('/todos', john_doe), { author_id: author_2.id }
expect(response.status).to eq(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
end
@@ -64,6 +66,7 @@ describe API::Todos, api: true do
get api('/todos', john_doe), { type: 'MergeRequest' }
expect(response.status).to eq(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
end
@@ -74,6 +77,7 @@ describe API::Todos, api: true do
get api('/todos', john_doe), { state: 'done' }
expect(response.status).to eq(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
end
@@ -84,6 +88,7 @@ describe API::Todos, api: true do
get api('/todos', john_doe), { project_id: project_2.id }
expect(response.status).to eq(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
end
@@ -94,6 +99,7 @@ describe API::Todos, api: true do
get api('/todos', john_doe), { action: 'mentioned' }
expect(response.status).to eq(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
end
@@ -101,46 +107,47 @@ describe API::Todos, api: true do
end
end
- describe 'DELETE /todos/:id' do
+ describe 'POST /todos/:id/mark_as_done' do
context 'when unauthenticated' do
it 'returns authentication error' do
- delete api("/todos/#{pending_1.id}")
+ post api("/todos/#{pending_1.id}/mark_as_done")
- expect(response.status).to eq(401)
+ expect(response).to have_http_status(401)
end
end
context 'when authenticated' do
it 'marks a todo as done' do
- delete api("/todos/#{pending_1.id}", john_doe)
+ post api("/todos/#{pending_1.id}/mark_as_done", john_doe)
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(201)
+ expect(json_response['id']).to eq(pending_1.id)
+ expect(json_response['state']).to eq('done')
expect(pending_1.reload).to be_done
end
it 'updates todos cache' do
expect_any_instance_of(User).to receive(:update_todos_count_cache).and_call_original
- delete api("/todos/#{pending_1.id}", john_doe)
+ post api("/todos/#{pending_1.id}/mark_as_done", john_doe)
end
end
end
- describe 'DELETE /todos' do
+ describe 'POST /mark_as_done' do
context 'when unauthenticated' do
it 'returns authentication error' do
- delete api('/todos')
+ post api('/todos/mark_as_done')
- expect(response.status).to eq(401)
+ expect(response).to have_http_status(401)
end
end
context 'when authenticated' do
it 'marks all todos as done' do
- delete api('/todos', john_doe)
+ post api('/todos/mark_as_done', john_doe)
- expect(response.status).to eq(200)
- expect(response.body).to eq('3')
+ expect(response).to have_http_status(204)
expect(pending_1.reload).to be_done
expect(pending_2.reload).to be_done
expect(pending_3.reload).to be_done
@@ -149,7 +156,7 @@ describe API::Todos, api: true do
it 'updates todos cache' do
expect_any_instance_of(User).to receive(:update_todos_count_cache).and_call_original
- delete api("/todos", john_doe)
+ post api("/todos/mark_as_done", john_doe)
end
end
end
diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb
index 84104aa66ee..92dfc2aa277 100644
--- a/spec/requests/api/triggers_spec.rb
+++ b/spec/requests/api/triggers_spec.rb
@@ -100,6 +100,7 @@ describe API::Triggers do
get api("/projects/#{project.id}/triggers", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_a(Array)
expect(json_response[0]).to have_key('token')
end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 8692f9da976..603da9f49fc 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -40,7 +40,9 @@ describe API::Users, api: true do
it "returns an array of users" do
get api("/users", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
username = user.username
expect(json_response.detect do |user|
@@ -55,13 +57,16 @@ describe API::Users, api: true do
get api("/users?blocked=true", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response).to all(include('state' => /(blocked|ldap_blocked)/))
end
it "returns one user" do
get api("/users?username=#{omniauth_user.username}", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['username']).to eq(omniauth_user.username)
end
@@ -70,7 +75,9 @@ describe API::Users, api: true do
context "when admin" do
it "returns an array of users" do
get api("/users", admin)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first.keys).to include 'email'
expect(json_response.first.keys).to include 'organization'
@@ -87,6 +94,7 @@ describe API::Users, api: true do
get api("/users?external=true", admin)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response).to all(include('external' => true))
end
@@ -190,6 +198,18 @@ describe API::Users, api: true do
expect(new_user.external).to be_truthy
end
+ it "creates user with reset password" do
+ post api('/users', admin), attributes_for(:user, reset_password: true).except(:password)
+
+ expect(response).to have_http_status(201)
+
+ user_id = json_response['id']
+ new_user = User.find(user_id)
+
+ expect(new_user).not_to eq(nil)
+ expect(new_user.recently_sent_password_reset?).to eq(true)
+ end
+
it "does not create user with invalid email" do
post api('/users', admin),
email: 'invalid email',
@@ -495,8 +515,11 @@ describe API::Users, api: true do
it 'returns array of ssh keys' do
user.keys << key
user.save
+
get api("/users/#{user.id}/keys", admin)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['title']).to eq(key.title)
end
@@ -583,8 +606,11 @@ describe API::Users, api: true do
it 'returns array of emails' do
user.emails << email
user.save
+
get api("/users/#{user.id}/emails", admin)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['email']).to eq(email.email)
end
@@ -762,8 +788,11 @@ describe API::Users, api: true do
it "returns array of ssh keys" do
user.keys << key
user.save
+
get api("/user/keys", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first["title"]).to eq(key.title)
end
@@ -879,8 +908,11 @@ describe API::Users, api: true do
it "returns array of emails" do
user.emails << email
user.save
+
get api("/user/emails", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first["email"]).to eq(email.email)
end
@@ -971,69 +1003,69 @@ describe API::Users, api: true do
end
end
- describe 'PUT /users/:id/block' do
+ describe 'POST /users/:id/block' do
before { admin }
it 'blocks existing user' do
- put api("/users/#{user.id}/block", admin)
- expect(response).to have_http_status(200)
+ post api("/users/#{user.id}/block", admin)
+ expect(response).to have_http_status(201)
expect(user.reload.state).to eq('blocked')
end
it 'does not re-block ldap blocked users' do
- put api("/users/#{ldap_blocked_user.id}/block", admin)
+ post api("/users/#{ldap_blocked_user.id}/block", admin)
expect(response).to have_http_status(403)
expect(ldap_blocked_user.reload.state).to eq('ldap_blocked')
end
it 'does not be available for non admin users' do
- put api("/users/#{user.id}/block", user)
+ post api("/users/#{user.id}/block", user)
expect(response).to have_http_status(403)
expect(user.reload.state).to eq('active')
end
it 'returns a 404 error if user id not found' do
- put api('/users/9999/block', admin)
+ post api('/users/9999/block', admin)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
end
end
- describe 'PUT /users/:id/unblock' do
+ describe 'POST /users/:id/unblock' do
let(:blocked_user) { create(:user, state: 'blocked') }
before { admin }
it 'unblocks existing user' do
- put api("/users/#{user.id}/unblock", admin)
- expect(response).to have_http_status(200)
+ post api("/users/#{user.id}/unblock", admin)
+ expect(response).to have_http_status(201)
expect(user.reload.state).to eq('active')
end
it 'unblocks a blocked user' do
- put api("/users/#{blocked_user.id}/unblock", admin)
- expect(response).to have_http_status(200)
+ post api("/users/#{blocked_user.id}/unblock", admin)
+ expect(response).to have_http_status(201)
expect(blocked_user.reload.state).to eq('active')
end
it 'does not unblock ldap blocked users' do
- put api("/users/#{ldap_blocked_user.id}/unblock", admin)
+ post api("/users/#{ldap_blocked_user.id}/unblock", admin)
expect(response).to have_http_status(403)
expect(ldap_blocked_user.reload.state).to eq('ldap_blocked')
end
it 'does not be available for non admin users' do
- put api("/users/#{user.id}/unblock", user)
+ post api("/users/#{user.id}/unblock", user)
expect(response).to have_http_status(403)
expect(user.reload.state).to eq('active')
end
it 'returns a 404 error if user id not found' do
- put api('/users/9999/block', admin)
+ post api('/users/9999/block', admin)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
end
it "returns a 404 for invalid ID" do
- put api("/users/ASDF/block", admin)
+ post api("/users/ASDF/block", admin)
expect(response).to have_http_status(404)
end
@@ -1061,14 +1093,14 @@ describe API::Users, api: true do
end
context "as a user than can see the event's project" do
- it_behaves_like 'a paginated resources' do
- let(:request) { get api("/users/#{user.id}/events", user) }
- end
-
context 'joined event' do
it 'returns the "joined" event' do
get api("/users/#{user.id}/events", user)
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+
comment_event = json_response.find { |e| e['action_name'] == 'commented on' }
expect(comment_event['project_id'].to_i).to eq(project.id)
diff --git a/spec/requests/api/v3/boards_spec.rb b/spec/requests/api/v3/boards_spec.rb
new file mode 100644
index 00000000000..8aaf3be4f87
--- /dev/null
+++ b/spec/requests/api/v3/boards_spec.rb
@@ -0,0 +1,79 @@
+require 'spec_helper'
+
+describe API::V3::Boards, api: true do
+ include ApiHelpers
+
+ let(:user) { create(:user) }
+ let(:guest) { create(:user) }
+ let!(:project) { create(:empty_project, :public, creator_id: user.id, namespace: user.namespace ) }
+
+ let!(:dev_label) do
+ create(:label, title: 'Development', color: '#FFAABB', project: project)
+ end
+
+ let!(:test_label) do
+ create(:label, title: 'Testing', color: '#FFAACC', project: project)
+ end
+
+ let!(:dev_list) do
+ create(:list, label: dev_label, position: 1)
+ end
+
+ let!(:test_list) do
+ create(:list, label: test_label, position: 2)
+ end
+
+ let!(:board) do
+ create(:board, project: project, lists: [dev_list, test_list])
+ end
+
+ before do
+ project.team << [user, :reporter]
+ project.team << [guest, :guest]
+ end
+
+ describe "GET /projects/:id/boards" do
+ let(:base_url) { "/projects/#{project.id}/boards" }
+
+ context "when unauthenticated" do
+ it "returns authentication error" do
+ get v3_api(base_url)
+
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context "when authenticated" do
+ it "returns the project issue board" do
+ get v3_api(base_url, user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['id']).to eq(board.id)
+ expect(json_response.first['lists']).to be_an Array
+ expect(json_response.first['lists'].length).to eq(2)
+ expect(json_response.first['lists'].last).to have_key('position')
+ end
+ end
+ end
+
+ describe "GET /projects/:id/boards/:board_id/lists" do
+ let(:base_url) { "/projects/#{project.id}/boards/#{board.id}/lists" }
+
+ it 'returns issue board lists' do
+ get v3_api(base_url, user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(2)
+ expect(json_response.first['label']['name']).to eq(dev_label.title)
+ end
+
+ it 'returns 404 if board not found' do
+ get v3_api("/projects/#{project.id}/boards/22343/lists", user)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+end
diff --git a/spec/requests/api/v3/branches_spec.rb b/spec/requests/api/v3/branches_spec.rb
new file mode 100644
index 00000000000..a3e1581fcc5
--- /dev/null
+++ b/spec/requests/api/v3/branches_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+require 'mime/types'
+
+describe API::V3::Branches, api: true do
+ include ApiHelpers
+
+ let(:user) { create(:user) }
+ let!(:project) { create(:project, :repository, creator: user) }
+ let!(:master) { create(:project_member, :master, user: user, project: project) }
+
+ describe "GET /projects/:id/repository/branches" do
+ it "returns an array of project branches" do
+ project.repository.expire_all_method_caches
+
+ get v3_api("/projects/#{project.id}/repository/branches", user), per_page: 100
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ branch_names = json_response.map { |x| x['name'] }
+ expect(branch_names).to match_array(project.repository.branch_names)
+ end
+ end
+
+ describe "DELETE /projects/:id/repository/merged_branches" do
+ before do
+ allow_any_instance_of(Repository).to receive(:rm_branch).and_return(true)
+ end
+
+ it 'returns 200' do
+ delete v3_api("/projects/#{project.id}/repository/merged_branches", user)
+
+ expect(response).to have_http_status(200)
+ end
+
+ it 'returns a 403 error if guest' do
+ user_b = create :user
+ create(:project_member, :guest, user: user_b, project: project)
+
+ delete v3_api("/projects/#{project.id}/repository/merged_branches", user_b)
+
+ expect(response).to have_http_status(403)
+ end
+ end
+end
diff --git a/spec/requests/api/v3/commits_spec.rb b/spec/requests/api/v3/commits_spec.rb
new file mode 100644
index 00000000000..e298ef055e1
--- /dev/null
+++ b/spec/requests/api/v3/commits_spec.rb
@@ -0,0 +1,578 @@
+require 'spec_helper'
+require 'mime/types'
+
+describe API::V3::Commits, api: true do
+ include ApiHelpers
+ let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+ let!(:project) { create(:project, :repository, creator: user, namespace: user.namespace) }
+ let!(:master) { create(:project_member, :master, user: user, project: project) }
+ let!(:guest) { create(:project_member, :guest, user: user2, project: project) }
+ let!(:note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'a comment on a commit') }
+ let!(:another_note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'another comment on a commit') }
+
+ before { project.team << [user, :reporter] }
+
+ describe "List repository commits" do
+ context "authorized user" do
+ before { project.team << [user2, :reporter] }
+
+ it "returns project commits" do
+ commit = project.repository.commit
+ get v3_api("/projects/#{project.id}/repository/commits", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['id']).to eq(commit.id)
+ expect(json_response.first['committer_name']).to eq(commit.committer_name)
+ expect(json_response.first['committer_email']).to eq(commit.committer_email)
+ end
+ end
+
+ context "unauthorized user" do
+ it "does not return project commits" do
+ get v3_api("/projects/#{project.id}/repository/commits")
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context "since optional parameter" do
+ it "returns project commits since provided parameter" do
+ commits = project.repository.commits("master")
+ since = commits.second.created_at
+
+ get v3_api("/projects/#{project.id}/repository/commits?since=#{since.utc.iso8601}", user)
+
+ expect(json_response.size).to eq 2
+ expect(json_response.first["id"]).to eq(commits.first.id)
+ expect(json_response.second["id"]).to eq(commits.second.id)
+ end
+ end
+
+ context "until optional parameter" do
+ it "returns project commits until provided parameter" do
+ commits = project.repository.commits("master")
+ before = commits.second.created_at
+
+ get v3_api("/projects/#{project.id}/repository/commits?until=#{before.utc.iso8601}", user)
+
+ if commits.size >= 20
+ expect(json_response.size).to eq(20)
+ else
+ expect(json_response.size).to eq(commits.size - 1)
+ end
+
+ expect(json_response.first["id"]).to eq(commits.second.id)
+ expect(json_response.second["id"]).to eq(commits.third.id)
+ end
+ end
+
+ context "invalid xmlschema date parameters" do
+ it "returns an invalid parameter error message" do
+ get v3_api("/projects/#{project.id}/repository/commits?since=invalid-date", user)
+
+ expect(response).to have_http_status(400)
+ expect(json_response['error']).to eq('since is invalid')
+ end
+ end
+
+ context "path optional parameter" do
+ it "returns project commits matching provided path parameter" do
+ path = 'files/ruby/popen.rb'
+
+ get v3_api("/projects/#{project.id}/repository/commits?path=#{path}", user)
+
+ expect(json_response.size).to eq(3)
+ expect(json_response.first["id"]).to eq("570e7b2abdd848b95f2f578043fc23bd6f6fd24d")
+ end
+ end
+ end
+
+ describe "Create a commit with multiple files and actions" do
+ let!(:url) { "/projects/#{project.id}/repository/commits" }
+
+ it 'returns a 403 unauthorized for user without permissions' do
+ post v3_api(url, user2)
+
+ expect(response).to have_http_status(403)
+ end
+
+ it 'returns a 400 bad request if no params are given' do
+ post v3_api(url, user)
+
+ expect(response).to have_http_status(400)
+ end
+
+ context :create do
+ let(:message) { 'Created file' }
+ let!(:invalid_c_params) do
+ {
+ branch_name: 'master',
+ commit_message: message,
+ actions: [
+ {
+ action: 'create',
+ file_path: 'files/ruby/popen.rb',
+ content: 'puts 8'
+ }
+ ]
+ }
+ end
+ let!(:valid_c_params) do
+ {
+ branch_name: 'master',
+ commit_message: message,
+ actions: [
+ {
+ action: 'create',
+ file_path: 'foo/bar/baz.txt',
+ content: 'puts 8'
+ }
+ ]
+ }
+ end
+
+ it 'a new file in project repo' do
+ post v3_api(url, user), valid_c_params
+
+ expect(response).to have_http_status(201)
+ expect(json_response['title']).to eq(message)
+ expect(json_response['committer_name']).to eq(user.name)
+ expect(json_response['committer_email']).to eq(user.email)
+ end
+
+ it 'returns a 400 bad request if file exists' do
+ post v3_api(url, user), invalid_c_params
+
+ expect(response).to have_http_status(400)
+ end
+
+ context 'with project path in URL' do
+ let(:url) { "/projects/#{project.full_path.gsub('/', '%2F')}/repository/commits" }
+
+ it 'a new file in project repo' do
+ post v3_api(url, user), valid_c_params
+
+ expect(response).to have_http_status(201)
+ end
+ end
+ end
+
+ context :delete do
+ let(:message) { 'Deleted file' }
+ let!(:invalid_d_params) do
+ {
+ branch_name: 'markdown',
+ commit_message: message,
+ actions: [
+ {
+ action: 'delete',
+ file_path: 'doc/api/projects.md'
+ }
+ ]
+ }
+ end
+ let!(:valid_d_params) do
+ {
+ branch_name: 'markdown',
+ commit_message: message,
+ actions: [
+ {
+ action: 'delete',
+ file_path: 'doc/api/users.md'
+ }
+ ]
+ }
+ end
+
+ it 'an existing file in project repo' do
+ post v3_api(url, user), valid_d_params
+
+ expect(response).to have_http_status(201)
+ expect(json_response['title']).to eq(message)
+ end
+
+ it 'returns a 400 bad request if file does not exist' do
+ post v3_api(url, user), invalid_d_params
+
+ expect(response).to have_http_status(400)
+ end
+ end
+
+ context :move do
+ let(:message) { 'Moved file' }
+ let!(:invalid_m_params) do
+ {
+ branch_name: 'feature',
+ commit_message: message,
+ actions: [
+ {
+ action: 'move',
+ file_path: 'CHANGELOG',
+ previous_path: 'VERSION',
+ content: '6.7.0.pre'
+ }
+ ]
+ }
+ end
+ let!(:valid_m_params) do
+ {
+ branch_name: 'feature',
+ commit_message: message,
+ actions: [
+ {
+ action: 'move',
+ file_path: 'VERSION.txt',
+ previous_path: 'VERSION',
+ content: '6.7.0.pre'
+ }
+ ]
+ }
+ end
+
+ it 'an existing file in project repo' do
+ post v3_api(url, user), valid_m_params
+
+ expect(response).to have_http_status(201)
+ expect(json_response['title']).to eq(message)
+ end
+
+ it 'returns a 400 bad request if file does not exist' do
+ post v3_api(url, user), invalid_m_params
+
+ expect(response).to have_http_status(400)
+ end
+ end
+
+ context :update do
+ let(:message) { 'Updated file' }
+ let!(:invalid_u_params) do
+ {
+ branch_name: 'master',
+ commit_message: message,
+ actions: [
+ {
+ action: 'update',
+ file_path: 'foo/bar.baz',
+ content: 'puts 8'
+ }
+ ]
+ }
+ end
+ let!(:valid_u_params) do
+ {
+ branch_name: 'master',
+ commit_message: message,
+ actions: [
+ {
+ action: 'update',
+ file_path: 'files/ruby/popen.rb',
+ content: 'puts 8'
+ }
+ ]
+ }
+ end
+
+ it 'an existing file in project repo' do
+ post v3_api(url, user), valid_u_params
+
+ expect(response).to have_http_status(201)
+ expect(json_response['title']).to eq(message)
+ end
+
+ it 'returns a 400 bad request if file does not exist' do
+ post v3_api(url, user), invalid_u_params
+
+ expect(response).to have_http_status(400)
+ end
+ end
+
+ context "multiple operations" do
+ let(:message) { 'Multiple actions' }
+ let!(:invalid_mo_params) do
+ {
+ branch_name: 'master',
+ commit_message: message,
+ actions: [
+ {
+ action: 'create',
+ file_path: 'files/ruby/popen.rb',
+ content: 'puts 8'
+ },
+ {
+ action: 'delete',
+ file_path: 'doc/v3_api/projects.md'
+ },
+ {
+ action: 'move',
+ file_path: 'CHANGELOG',
+ previous_path: 'VERSION',
+ content: '6.7.0.pre'
+ },
+ {
+ action: 'update',
+ file_path: 'foo/bar.baz',
+ content: 'puts 8'
+ }
+ ]
+ }
+ end
+ let!(:valid_mo_params) do
+ {
+ branch_name: 'master',
+ commit_message: message,
+ actions: [
+ {
+ action: 'create',
+ file_path: 'foo/bar/baz.txt',
+ content: 'puts 8'
+ },
+ {
+ action: 'delete',
+ file_path: 'Gemfile.zip'
+ },
+ {
+ action: 'move',
+ file_path: 'VERSION.txt',
+ previous_path: 'VERSION',
+ content: '6.7.0.pre'
+ },
+ {
+ action: 'update',
+ file_path: 'files/ruby/popen.rb',
+ content: 'puts 8'
+ }
+ ]
+ }
+ end
+
+ it 'are commited as one in project repo' do
+ post v3_api(url, user), valid_mo_params
+
+ expect(response).to have_http_status(201)
+ expect(json_response['title']).to eq(message)
+ end
+
+ it 'return a 400 bad request if there are any issues' do
+ post v3_api(url, user), invalid_mo_params
+
+ expect(response).to have_http_status(400)
+ end
+ end
+ end
+
+ describe "Get a single commit" do
+ context "authorized user" do
+ it "returns a commit by sha" do
+ get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['id']).to eq(project.repository.commit.id)
+ expect(json_response['title']).to eq(project.repository.commit.title)
+ expect(json_response['stats']['additions']).to eq(project.repository.commit.stats.additions)
+ expect(json_response['stats']['deletions']).to eq(project.repository.commit.stats.deletions)
+ expect(json_response['stats']['total']).to eq(project.repository.commit.stats.total)
+ end
+
+ it "returns a 404 error if not found" do
+ get v3_api("/projects/#{project.id}/repository/commits/invalid_sha", user)
+ expect(response).to have_http_status(404)
+ end
+
+ it "returns nil for commit without CI" do
+ get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['status']).to be_nil
+ end
+
+ it "returns status for CI" do
+ pipeline = project.ensure_pipeline('master', project.repository.commit.sha)
+ pipeline.update(status: 'success')
+
+ get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['status']).to eq(pipeline.status)
+ end
+
+ it "returns status for CI when pipeline is created" do
+ project.ensure_pipeline('master', project.repository.commit.sha)
+
+ get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['status']).to eq("created")
+ end
+ end
+
+ context "unauthorized user" do
+ it "does not return the selected commit" do
+ get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}")
+ expect(response).to have_http_status(401)
+ end
+ end
+ end
+
+ describe "Get the diff of a commit" do
+ context "authorized user" do
+ before { project.team << [user2, :reporter] }
+
+ it "returns the diff of the selected commit" do
+ get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/diff", user)
+ expect(response).to have_http_status(200)
+
+ expect(json_response).to be_an Array
+ expect(json_response.length).to be >= 1
+ expect(json_response.first.keys).to include "diff"
+ end
+
+ it "returns a 404 error if invalid commit" do
+ get v3_api("/projects/#{project.id}/repository/commits/invalid_sha/diff", user)
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context "unauthorized user" do
+ it "does not return the diff of the selected commit" do
+ get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/diff")
+ expect(response).to have_http_status(401)
+ end
+ end
+ end
+
+ describe 'Get the comments of a commit' do
+ context 'authorized user' do
+ it 'returns merge_request comments' do
+ get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user)
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(2)
+ expect(json_response.first['note']).to eq('a comment on a commit')
+ expect(json_response.first['author']['id']).to eq(user.id)
+ end
+
+ it 'returns a 404 error if merge_request_id not found' do
+ get v3_api("/projects/#{project.id}/repository/commits/1234ab/comments", user)
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'does not return the diff of the selected commit' do
+ get v3_api("/projects/#{project.id}/repository/commits/1234ab/comments")
+ expect(response).to have_http_status(401)
+ end
+ end
+ end
+
+ describe 'POST :id/repository/commits/:sha/cherry_pick' do
+ let(:master_pickable_commit) { project.commit('7d3b0f7cff5f37573aea97cebfd5692ea1689924') }
+
+ context 'authorized user' do
+ it 'cherry picks a commit' do
+ post v3_api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user), branch: 'master'
+
+ expect(response).to have_http_status(201)
+ expect(json_response['title']).to eq(master_pickable_commit.title)
+ expect(json_response['message']).to eq(master_pickable_commit.message)
+ expect(json_response['author_name']).to eq(master_pickable_commit.author_name)
+ expect(json_response['committer_name']).to eq(user.name)
+ end
+
+ it 'returns 400 if commit is already included in the target branch' do
+ post v3_api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user), branch: 'markdown'
+
+ expect(response).to have_http_status(400)
+ expect(json_response['message']).to eq('Sorry, we cannot cherry-pick this commit automatically.
+ A cherry-pick may have already been performed with this commit, or a more recent commit may have updated some of its content.')
+ end
+
+ it 'returns 400 if you are not allowed to push to the target branch' do
+ project.team << [user2, :developer]
+ protected_branch = create(:protected_branch, project: project, name: 'feature')
+
+ post v3_api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user2), branch: protected_branch.name
+
+ expect(response).to have_http_status(400)
+ expect(json_response['message']).to eq('You are not allowed to push into this branch')
+ end
+
+ it 'returns 400 for missing parameters' do
+ post v3_api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user)
+
+ expect(response).to have_http_status(400)
+ expect(json_response['error']).to eq('branch is missing')
+ end
+
+ it 'returns 404 if commit is not found' do
+ post v3_api("/projects/#{project.id}/repository/commits/abcd0123/cherry_pick", user), branch: 'master'
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 Commit Not Found')
+ end
+
+ it 'returns 404 if branch is not found' do
+ post v3_api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user), branch: 'foo'
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 Branch Not Found')
+ end
+
+ it 'returns 400 for missing parameters' do
+ post v3_api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user)
+
+ expect(response).to have_http_status(400)
+ expect(json_response['error']).to eq('branch is missing')
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'does not cherry pick the commit' do
+ post v3_api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick"), branch: 'master'
+
+ expect(response).to have_http_status(401)
+ end
+ end
+ end
+
+ describe 'Post comment to commit' do
+ context 'authorized user' do
+ it 'returns comment' do
+ post v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment'
+ expect(response).to have_http_status(201)
+ expect(json_response['note']).to eq('My comment')
+ expect(json_response['path']).to be_nil
+ expect(json_response['line']).to be_nil
+ expect(json_response['line_type']).to be_nil
+ end
+
+ it 'returns the inline comment' do
+ post v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment', path: project.repository.commit.raw_diffs.first.new_path, line: 1, line_type: 'new'
+
+ expect(response).to have_http_status(201)
+ expect(json_response['note']).to eq('My comment')
+ expect(json_response['path']).to eq(project.repository.commit.raw_diffs.first.new_path)
+ expect(json_response['line']).to eq(1)
+ expect(json_response['line_type']).to eq('new')
+ end
+
+ it 'returns 400 if note is missing' do
+ post v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user)
+ expect(response).to have_http_status(400)
+ end
+
+ it 'returns 404 if note is attached to non existent commit' do
+ post v3_api("/projects/#{project.id}/repository/commits/1234ab/comments", user), note: 'My comment'
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'does not return the diff of the selected commit' do
+ post v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments")
+ expect(response).to have_http_status(401)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/v3/files_spec.rb b/spec/requests/api/v3/files_spec.rb
new file mode 100644
index 00000000000..4af05605ec6
--- /dev/null
+++ b/spec/requests/api/v3/files_spec.rb
@@ -0,0 +1,270 @@
+require 'spec_helper'
+
+describe API::V3::Files, api: true do
+ include ApiHelpers
+ let(:user) { create(:user) }
+ let!(:project) { create(:project, :repository, namespace: user.namespace ) }
+ let(:guest) { create(:user) { |u| project.add_guest(u) } }
+ let(:file_path) { 'files/ruby/popen.rb' }
+ let(:params) do
+ {
+ file_path: file_path,
+ ref: 'master'
+ }
+ end
+ let(:author_email) { FFaker::Internet.email }
+
+ # I have to remove periods from the end of the name
+ # This happened when the user's name had a suffix (i.e. "Sr.")
+ # This seems to be what git does under the hood. For example, this commit:
+ #
+ # $ git commit --author='Foo Sr. <foo@example.com>' -m 'Where's my trailing period?'
+ #
+ # results in this:
+ #
+ # $ git show --pretty
+ # ...
+ # Author: Foo Sr <foo@example.com>
+ # ...
+ let(:author_name) { FFaker::Name.name.chomp("\.") }
+
+ before { project.team << [user, :developer] }
+
+ describe "GET /projects/:id/repository/files" do
+ let(:route) { "/projects/#{project.id}/repository/files" }
+
+ shared_examples_for 'repository files' do
+ it "returns file info" do
+ get v3_api(route, current_user), params
+
+ expect(response).to have_http_status(200)
+ expect(json_response['file_path']).to eq(file_path)
+ expect(json_response['file_name']).to eq('popen.rb')
+ expect(json_response['last_commit_id']).to eq('570e7b2abdd848b95f2f578043fc23bd6f6fd24d')
+ expect(Base64.decode64(json_response['content']).lines.first).to eq("require 'fileutils'\n")
+ end
+
+ context 'when no params are given' do
+ it_behaves_like '400 response' do
+ let(:request) { get v3_api(route, current_user) }
+ end
+ end
+
+ context 'when file_path does not exist' do
+ let(:params) do
+ {
+ file_path: 'app/models/application.rb',
+ ref: 'master',
+ }
+ end
+
+ it_behaves_like '404 response' do
+ let(:request) { get v3_api(route, current_user), params }
+ let(:message) { '404 File Not Found' }
+ end
+ end
+
+ context 'when repository is disabled' do
+ include_context 'disabled repository'
+
+ it_behaves_like '403 response' do
+ let(:request) { get v3_api(route, current_user), params }
+ end
+ end
+ end
+
+ context 'when unauthenticated', 'and project is public' do
+ it_behaves_like 'repository files' do
+ let(:project) { create(:project, :public) }
+ let(:current_user) { nil }
+ end
+ end
+
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { get v3_api(route), params }
+ let(:message) { '404 Project Not Found' }
+ end
+ end
+
+ context 'when authenticated', 'as a developer' do
+ it_behaves_like 'repository files' do
+ let(:current_user) { user }
+ end
+ end
+
+ context 'when authenticated', 'as a guest' do
+ it_behaves_like '403 response' do
+ let(:request) { get v3_api(route, guest), params }
+ end
+ end
+ end
+
+ describe "POST /projects/:id/repository/files" do
+ let(:valid_params) do
+ {
+ file_path: 'newfile.rb',
+ branch_name: 'master',
+ content: 'puts 8',
+ commit_message: 'Added newfile'
+ }
+ end
+
+ it "creates a new file in project repo" do
+ post v3_api("/projects/#{project.id}/repository/files", user), valid_params
+
+ expect(response).to have_http_status(201)
+ expect(json_response['file_path']).to eq('newfile.rb')
+ last_commit = project.repository.commit.raw
+ expect(last_commit.author_email).to eq(user.email)
+ expect(last_commit.author_name).to eq(user.name)
+ end
+
+ it "returns a 400 bad request if no params given" do
+ post v3_api("/projects/#{project.id}/repository/files", user)
+
+ expect(response).to have_http_status(400)
+ end
+
+ it "returns a 400 if editor fails to create file" do
+ allow_any_instance_of(Repository).to receive(:commit_file).
+ and_return(false)
+
+ post v3_api("/projects/#{project.id}/repository/files", user), valid_params
+
+ expect(response).to have_http_status(400)
+ end
+
+ context "when specifying an author" do
+ it "creates a new file with the specified author" do
+ valid_params.merge!(author_email: author_email, author_name: author_name)
+
+ post v3_api("/projects/#{project.id}/repository/files", user), valid_params
+
+ expect(response).to have_http_status(201)
+ last_commit = project.repository.commit.raw
+ expect(last_commit.author_email).to eq(author_email)
+ expect(last_commit.author_name).to eq(author_name)
+ end
+ end
+ end
+
+ describe "PUT /projects/:id/repository/files" do
+ let(:valid_params) do
+ {
+ file_path: file_path,
+ branch_name: 'master',
+ content: 'puts 8',
+ commit_message: 'Changed file'
+ }
+ end
+
+ it "updates existing file in project repo" do
+ put v3_api("/projects/#{project.id}/repository/files", user), valid_params
+
+ expect(response).to have_http_status(200)
+ expect(json_response['file_path']).to eq(file_path)
+ last_commit = project.repository.commit.raw
+ expect(last_commit.author_email).to eq(user.email)
+ expect(last_commit.author_name).to eq(user.name)
+ end
+
+ it "returns a 400 bad request if no params given" do
+ put v3_api("/projects/#{project.id}/repository/files", user)
+
+ expect(response).to have_http_status(400)
+ end
+
+ context "when specifying an author" do
+ it "updates a file with the specified author" do
+ valid_params.merge!(author_email: author_email, author_name: author_name, content: "New content")
+
+ put v3_api("/projects/#{project.id}/repository/files", user), valid_params
+
+ expect(response).to have_http_status(200)
+ last_commit = project.repository.commit.raw
+ expect(last_commit.author_email).to eq(author_email)
+ expect(last_commit.author_name).to eq(author_name)
+ end
+ end
+ end
+
+ describe "DELETE /projects/:id/repository/files" do
+ let(:valid_params) do
+ {
+ file_path: file_path,
+ branch_name: 'master',
+ commit_message: 'Changed file'
+ }
+ end
+
+ it "deletes existing file in project repo" do
+ delete v3_api("/projects/#{project.id}/repository/files", user), valid_params
+
+ expect(response).to have_http_status(200)
+ expect(json_response['file_path']).to eq(file_path)
+ last_commit = project.repository.commit.raw
+ expect(last_commit.author_email).to eq(user.email)
+ expect(last_commit.author_name).to eq(user.name)
+ end
+
+ it "returns a 400 bad request if no params given" do
+ delete v3_api("/projects/#{project.id}/repository/files", user)
+
+ expect(response).to have_http_status(400)
+ end
+
+ it "returns a 400 if fails to create file" do
+ allow_any_instance_of(Repository).to receive(:remove_file).and_return(false)
+
+ delete v3_api("/projects/#{project.id}/repository/files", user), valid_params
+
+ expect(response).to have_http_status(400)
+ end
+
+ context "when specifying an author" do
+ it "removes a file with the specified author" do
+ valid_params.merge!(author_email: author_email, author_name: author_name)
+
+ delete v3_api("/projects/#{project.id}/repository/files", user), valid_params
+
+ expect(response).to have_http_status(200)
+ last_commit = project.repository.commit.raw
+ expect(last_commit.author_email).to eq(author_email)
+ expect(last_commit.author_name).to eq(author_name)
+ end
+ end
+ end
+
+ describe "POST /projects/:id/repository/files with binary file" do
+ let(:file_path) { 'test.bin' }
+ let(:put_params) do
+ {
+ file_path: file_path,
+ branch_name: 'master',
+ content: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=',
+ commit_message: 'Binary file with a \n should not be touched',
+ encoding: 'base64'
+ }
+ end
+ let(:get_params) do
+ {
+ file_path: file_path,
+ ref: 'master',
+ }
+ end
+
+ before do
+ post v3_api("/projects/#{project.id}/repository/files", user), put_params
+ end
+
+ it "remains unchanged" do
+ get v3_api("/projects/#{project.id}/repository/files", user), get_params
+
+ expect(response).to have_http_status(200)
+ expect(json_response['file_path']).to eq(file_path)
+ expect(json_response['file_name']).to eq(file_path)
+ expect(json_response['content']).to eq(put_params[:content])
+ end
+ end
+end
diff --git a/spec/requests/api/v3/groups_spec.rb b/spec/requests/api/v3/groups_spec.rb
new file mode 100644
index 00000000000..8b29ad03737
--- /dev/null
+++ b/spec/requests/api/v3/groups_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+
+describe API::V3::Groups, api: true do
+ include ApiHelpers
+ include UploadHelpers
+
+ let(:user2) { create(:user) }
+ let!(:group2) { create(:group, :private) }
+ let!(:project2) { create(:empty_project, namespace: group2) }
+
+ before do
+ group2.add_owner(user2)
+ end
+
+ describe 'GET /groups/owned' do
+ context 'when unauthenticated' do
+ it 'returns authentication error' do
+ get v3_api('/groups/owned')
+
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context 'when authenticated as group owner' do
+ it 'returns an array of groups the user owns' do
+ get v3_api('/groups/owned', user2)
+
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.first['name']).to eq(group2.name)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/v3/issues_spec.rb b/spec/requests/api/v3/issues_spec.rb
index 33a127de98a..803acd55470 100644
--- a/spec/requests/api/v3/issues_spec.rb
+++ b/spec/requests/api/v3/issues_spec.rb
@@ -722,7 +722,7 @@ describe API::V3::Issues, api: true do
expect(response).to have_http_status(201)
expect(json_response['title']).to eq('new issue')
expect(json_response['description']).to be_nil
- expect(json_response['labels']).to eq(['label', 'label2'])
+ expect(json_response['labels']).to eq(%w(label label2))
expect(json_response['confidential']).to be_falsy
end
@@ -986,6 +986,33 @@ describe API::V3::Issues, api: true do
end
end
+ describe 'PUT /projects/:id/issues/:issue_id with spam filtering' do
+ let(:params) do
+ {
+ title: 'updated title',
+ description: 'content here',
+ labels: 'label, label2'
+ }
+ end
+
+ it "does not create a new project issue" do
+ allow_any_instance_of(SpamService).to receive_messages(check_for_spam?: true)
+ allow_any_instance_of(AkismetService).to receive_messages(is_spam?: true)
+
+ put v3_api("/projects/#{project.id}/issues/#{issue.id}", user), params
+
+ expect(response).to have_http_status(400)
+ expect(json_response['message']).to eq({ "error" => "Spam detected" })
+
+ spam_logs = SpamLog.all
+ expect(spam_logs.count).to eq(1)
+ expect(spam_logs[0].title).to eq('updated title')
+ expect(spam_logs[0].description).to eq('content here')
+ expect(spam_logs[0].user).to eq(user)
+ expect(spam_logs[0].noteable_type).to eq('Issue')
+ end
+ end
+
describe 'PUT /projects/:id/issues/:issue_id to update labels' do
let!(:label) { create(:label, title: 'dummy', project: project) }
let!(:label_link) { create(:label_link, label: label, target: issue) }
diff --git a/spec/requests/api/v3/labels_spec.rb b/spec/requests/api/v3/labels_spec.rb
new file mode 100644
index 00000000000..f44403374e9
--- /dev/null
+++ b/spec/requests/api/v3/labels_spec.rb
@@ -0,0 +1,152 @@
+require 'spec_helper'
+
+describe API::V3::Labels, api: true do
+ include ApiHelpers
+
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project, creator_id: user.id, namespace: user.namespace) }
+ let!(:label1) { create(:label, title: 'label1', project: project) }
+ let!(:priority_label) { create(:label, title: 'bug', project: project, priority: 3) }
+
+ before do
+ project.team << [user, :master]
+ end
+
+ describe 'GET /projects/:id/labels' do
+ it 'returns all available labels to the project' do
+ group = create(:group)
+ group_label = create(:group_label, title: 'feature', group: group)
+ project.update(group: group)
+ create(:labeled_issue, project: project, labels: [group_label], author: user)
+ create(:labeled_issue, project: project, labels: [label1], author: user, state: :closed)
+ create(:labeled_merge_request, labels: [priority_label], author: user, source_project: project )
+
+ expected_keys = %w(
+ id name color description
+ open_issues_count closed_issues_count open_merge_requests_count
+ subscribed priority
+ )
+
+ get v3_api("/projects/#{project.id}/labels", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(3)
+ expect(json_response.first.keys).to match_array expected_keys
+ expect(json_response.map { |l| l['name'] }).to match_array([group_label.name, priority_label.name, label1.name])
+
+ label1_response = json_response.find { |l| l['name'] == label1.title }
+ group_label_response = json_response.find { |l| l['name'] == group_label.title }
+ priority_label_response = json_response.find { |l| l['name'] == priority_label.title }
+
+ expect(label1_response['open_issues_count']).to eq(0)
+ expect(label1_response['closed_issues_count']).to eq(1)
+ expect(label1_response['open_merge_requests_count']).to eq(0)
+ expect(label1_response['name']).to eq(label1.name)
+ expect(label1_response['color']).to be_present
+ expect(label1_response['description']).to be_nil
+ expect(label1_response['priority']).to be_nil
+ expect(label1_response['subscribed']).to be_falsey
+
+ expect(group_label_response['open_issues_count']).to eq(1)
+ expect(group_label_response['closed_issues_count']).to eq(0)
+ expect(group_label_response['open_merge_requests_count']).to eq(0)
+ expect(group_label_response['name']).to eq(group_label.name)
+ expect(group_label_response['color']).to be_present
+ expect(group_label_response['description']).to be_nil
+ expect(group_label_response['priority']).to be_nil
+ expect(group_label_response['subscribed']).to be_falsey
+
+ expect(priority_label_response['open_issues_count']).to eq(0)
+ expect(priority_label_response['closed_issues_count']).to eq(0)
+ expect(priority_label_response['open_merge_requests_count']).to eq(1)
+ expect(priority_label_response['name']).to eq(priority_label.name)
+ expect(priority_label_response['color']).to be_present
+ expect(priority_label_response['description']).to be_nil
+ expect(priority_label_response['priority']).to eq(3)
+ expect(priority_label_response['subscribed']).to be_falsey
+ end
+ end
+
+ describe "POST /projects/:id/labels/:label_id/subscription" do
+ context "when label_id is a label title" do
+ it "subscribes to the label" do
+ post v3_api("/projects/#{project.id}/labels/#{label1.title}/subscription", user)
+
+ expect(response).to have_http_status(201)
+ expect(json_response["name"]).to eq(label1.title)
+ expect(json_response["subscribed"]).to be_truthy
+ end
+ end
+
+ context "when label_id is a label ID" do
+ it "subscribes to the label" do
+ post v3_api("/projects/#{project.id}/labels/#{label1.id}/subscription", user)
+
+ expect(response).to have_http_status(201)
+ expect(json_response["name"]).to eq(label1.title)
+ expect(json_response["subscribed"]).to be_truthy
+ end
+ end
+
+ context "when user is already subscribed to label" do
+ before { label1.subscribe(user, project) }
+
+ it "returns 304" do
+ post v3_api("/projects/#{project.id}/labels/#{label1.id}/subscription", user)
+
+ expect(response).to have_http_status(304)
+ end
+ end
+
+ context "when label ID is not found" do
+ it "returns 404 error" do
+ post v3_api("/projects/#{project.id}/labels/1234/subscription", user)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ describe "DELETE /projects/:id/labels/:label_id/subscription" do
+ before { label1.subscribe(user, project) }
+
+ context "when label_id is a label title" do
+ it "unsubscribes from the label" do
+ delete v3_api("/projects/#{project.id}/labels/#{label1.title}/subscription", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response["name"]).to eq(label1.title)
+ expect(json_response["subscribed"]).to be_falsey
+ end
+ end
+
+ context "when label_id is a label ID" do
+ it "unsubscribes from the label" do
+ delete v3_api("/projects/#{project.id}/labels/#{label1.id}/subscription", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response["name"]).to eq(label1.title)
+ expect(json_response["subscribed"]).to be_falsey
+ end
+ end
+
+ context "when user is already unsubscribed from label" do
+ before { label1.unsubscribe(user, project) }
+
+ it "returns 304" do
+ delete v3_api("/projects/#{project.id}/labels/#{label1.id}/subscription", user)
+
+ expect(response).to have_http_status(304)
+ end
+ end
+
+ context "when label ID is not found" do
+ it "returns 404 error" do
+ delete v3_api("/projects/#{project.id}/labels/1234/subscription", user)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/v3/members_spec.rb b/spec/requests/api/v3/members_spec.rb
new file mode 100644
index 00000000000..28c3ca03960
--- /dev/null
+++ b/spec/requests/api/v3/members_spec.rb
@@ -0,0 +1,342 @@
+require 'spec_helper'
+
+describe API::Members, api: true do
+ include ApiHelpers
+
+ let(:master) { create(:user) }
+ let(:developer) { create(:user) }
+ let(:access_requester) { create(:user) }
+ let(:stranger) { create(:user) }
+
+ let(:project) do
+ create(:empty_project, :public, :access_requestable, creator_id: master.id, namespace: master.namespace) do |project|
+ project.team << [developer, :developer]
+ project.team << [master, :master]
+ project.request_access(access_requester)
+ end
+ end
+
+ let!(:group) do
+ create(:group, :public, :access_requestable) do |group|
+ group.add_developer(developer)
+ group.add_owner(master)
+ group.request_access(access_requester)
+ end
+ end
+
+ shared_examples 'GET /:sources/:id/members' do |source_type|
+ context "with :sources == #{source_type.pluralize}" do
+ it_behaves_like 'a 404 response when source is private' do
+ let(:route) { get v3_api("/#{source_type.pluralize}/#{source.id}/members", stranger) }
+ end
+
+ %i[master developer access_requester stranger].each do |type|
+ context "when authenticated as a #{type}" do
+ it 'returns 200' do
+ user = public_send(type)
+ get v3_api("/#{source_type.pluralize}/#{source.id}/members", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response.size).to eq(2)
+ expect(json_response.map { |u| u['id'] }).to match_array [master.id, developer.id]
+ end
+ end
+ end
+
+ it 'does not return invitees' do
+ create(:"#{source_type}_member", invite_token: '123', invite_email: 'test@abc.com', source: source, user: nil)
+
+ get v3_api("/#{source_type.pluralize}/#{source.id}/members", developer)
+
+ expect(response).to have_http_status(200)
+ expect(json_response.size).to eq(2)
+ expect(json_response.map { |u| u['id'] }).to match_array [master.id, developer.id]
+ end
+
+ it 'finds members with query string' do
+ get v3_api("/#{source_type.pluralize}/#{source.id}/members", developer), query: master.username
+
+ expect(response).to have_http_status(200)
+ expect(json_response.count).to eq(1)
+ expect(json_response.first['username']).to eq(master.username)
+ end
+ end
+ end
+
+ shared_examples 'GET /:sources/:id/members/:user_id' do |source_type|
+ context "with :sources == #{source_type.pluralize}" do
+ it_behaves_like 'a 404 response when source is private' do
+ let(:route) { get v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", stranger) }
+ end
+
+ context 'when authenticated as a non-member' do
+ %i[access_requester stranger].each do |type|
+ context "as a #{type}" do
+ it 'returns 200' do
+ user = public_send(type)
+ get v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", user)
+
+ expect(response).to have_http_status(200)
+ # User attributes
+ expect(json_response['id']).to eq(developer.id)
+ expect(json_response['name']).to eq(developer.name)
+ expect(json_response['username']).to eq(developer.username)
+ expect(json_response['state']).to eq(developer.state)
+ expect(json_response['avatar_url']).to eq(developer.avatar_url)
+ expect(json_response['web_url']).to eq(Gitlab::Routing.url_helpers.user_url(developer))
+
+ # Member attributes
+ expect(json_response['access_level']).to eq(Member::DEVELOPER)
+ end
+ end
+ end
+ end
+ end
+ end
+
+ shared_examples 'POST /:sources/:id/members' do |source_type|
+ context "with :sources == #{source_type.pluralize}" do
+ it_behaves_like 'a 404 response when source is private' do
+ let(:route) do
+ post v3_api("/#{source_type.pluralize}/#{source.id}/members", stranger),
+ user_id: access_requester.id, access_level: Member::MASTER
+ end
+ end
+
+ context 'when authenticated as a non-member or member with insufficient rights' do
+ %i[access_requester stranger developer].each do |type|
+ context "as a #{type}" do
+ it 'returns 403' do
+ user = public_send(type)
+ post v3_api("/#{source_type.pluralize}/#{source.id}/members", user),
+ user_id: access_requester.id, access_level: Member::MASTER
+
+ expect(response).to have_http_status(403)
+ end
+ end
+ end
+ end
+
+ context 'when authenticated as a master/owner' do
+ context 'and new member is already a requester' do
+ it 'transforms the requester into a proper member' do
+ expect do
+ post v3_api("/#{source_type.pluralize}/#{source.id}/members", master),
+ user_id: access_requester.id, access_level: Member::MASTER
+
+ expect(response).to have_http_status(201)
+ end.to change { source.members.count }.by(1)
+ expect(source.requesters.count).to eq(0)
+ expect(json_response['id']).to eq(access_requester.id)
+ expect(json_response['access_level']).to eq(Member::MASTER)
+ end
+ end
+
+ it 'creates a new member' do
+ expect do
+ post v3_api("/#{source_type.pluralize}/#{source.id}/members", master),
+ user_id: stranger.id, access_level: Member::DEVELOPER, expires_at: '2016-08-05'
+
+ expect(response).to have_http_status(201)
+ end.to change { source.members.count }.by(1)
+ expect(json_response['id']).to eq(stranger.id)
+ expect(json_response['access_level']).to eq(Member::DEVELOPER)
+ expect(json_response['expires_at']).to eq('2016-08-05')
+ end
+ end
+
+ it "returns #{source_type == 'project' ? 201 : 409} if member already exists" do
+ post v3_api("/#{source_type.pluralize}/#{source.id}/members", master),
+ user_id: master.id, access_level: Member::MASTER
+
+ expect(response).to have_http_status(source_type == 'project' ? 201 : 409)
+ end
+
+ it 'returns 400 when user_id is not given' do
+ post v3_api("/#{source_type.pluralize}/#{source.id}/members", master),
+ access_level: Member::MASTER
+
+ expect(response).to have_http_status(400)
+ end
+
+ it 'returns 400 when access_level is not given' do
+ post v3_api("/#{source_type.pluralize}/#{source.id}/members", master),
+ user_id: stranger.id
+
+ expect(response).to have_http_status(400)
+ end
+
+ it 'returns 422 when access_level is not valid' do
+ post v3_api("/#{source_type.pluralize}/#{source.id}/members", master),
+ user_id: stranger.id, access_level: 1234
+
+ expect(response).to have_http_status(422)
+ end
+ end
+ end
+
+ shared_examples 'PUT /:sources/:id/members/:user_id' do |source_type|
+ context "with :sources == #{source_type.pluralize}" do
+ it_behaves_like 'a 404 response when source is private' do
+ let(:route) do
+ put v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", stranger),
+ access_level: Member::MASTER
+ end
+ end
+
+ context 'when authenticated as a non-member or member with insufficient rights' do
+ %i[access_requester stranger developer].each do |type|
+ context "as a #{type}" do
+ it 'returns 403' do
+ user = public_send(type)
+ put v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", user),
+ access_level: Member::MASTER
+
+ expect(response).to have_http_status(403)
+ end
+ end
+ end
+ end
+
+ context 'when authenticated as a master/owner' do
+ it 'updates the member' do
+ put v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", master),
+ access_level: Member::MASTER, expires_at: '2016-08-05'
+
+ expect(response).to have_http_status(200)
+ expect(json_response['id']).to eq(developer.id)
+ expect(json_response['access_level']).to eq(Member::MASTER)
+ expect(json_response['expires_at']).to eq('2016-08-05')
+ end
+ end
+
+ it 'returns 409 if member does not exist' do
+ put v3_api("/#{source_type.pluralize}/#{source.id}/members/123", master),
+ access_level: Member::MASTER
+
+ expect(response).to have_http_status(404)
+ end
+
+ it 'returns 400 when access_level is not given' do
+ put v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", master)
+
+ expect(response).to have_http_status(400)
+ end
+
+ it 'returns 422 when access level is not valid' do
+ put v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", master),
+ access_level: 1234
+
+ expect(response).to have_http_status(422)
+ end
+ end
+ end
+
+ shared_examples 'DELETE /:sources/:id/members/:user_id' do |source_type|
+ context "with :sources == #{source_type.pluralize}" do
+ it_behaves_like 'a 404 response when source is private' do
+ let(:route) { delete v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", stranger) }
+ end
+
+ context 'when authenticated as a non-member or member with insufficient rights' do
+ %i[access_requester stranger].each do |type|
+ context "as a #{type}" do
+ it 'returns 403' do
+ user = public_send(type)
+ delete v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", user)
+
+ expect(response).to have_http_status(403)
+ end
+ end
+ end
+ end
+
+ context 'when authenticated as a member and deleting themself' do
+ it 'deletes the member' do
+ expect do
+ delete v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", developer)
+
+ expect(response).to have_http_status(200)
+ end.to change { source.members.count }.by(-1)
+ end
+ end
+
+ context 'when authenticated as a master/owner' do
+ context 'and member is a requester' do
+ it "returns #{source_type == 'project' ? 200 : 404}" do
+ expect do
+ delete v3_api("/#{source_type.pluralize}/#{source.id}/members/#{access_requester.id}", master)
+
+ expect(response).to have_http_status(source_type == 'project' ? 200 : 404)
+ end.not_to change { source.requesters.count }
+ end
+ end
+
+ it 'deletes the member' do
+ expect do
+ delete v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", master)
+
+ expect(response).to have_http_status(200)
+ end.to change { source.members.count }.by(-1)
+ end
+ end
+
+ it "returns #{source_type == 'project' ? 200 : 404} if member does not exist" do
+ delete v3_api("/#{source_type.pluralize}/#{source.id}/members/123", master)
+
+ expect(response).to have_http_status(source_type == 'project' ? 200 : 404)
+ end
+ end
+ end
+
+ it_behaves_like 'GET /:sources/:id/members', 'project' do
+ let(:source) { project }
+ end
+
+ it_behaves_like 'GET /:sources/:id/members', 'group' do
+ let(:source) { group }
+ end
+
+ it_behaves_like 'GET /:sources/:id/members/:user_id', 'project' do
+ let(:source) { project }
+ end
+
+ it_behaves_like 'GET /:sources/:id/members/:user_id', 'group' do
+ let(:source) { group }
+ end
+
+ it_behaves_like 'POST /:sources/:id/members', 'project' do
+ let(:source) { project }
+ end
+
+ it_behaves_like 'POST /:sources/:id/members', 'group' do
+ let(:source) { group }
+ end
+
+ it_behaves_like 'PUT /:sources/:id/members/:user_id', 'project' do
+ let(:source) { project }
+ end
+
+ it_behaves_like 'PUT /:sources/:id/members/:user_id', 'group' do
+ let(:source) { group }
+ end
+
+ it_behaves_like 'DELETE /:sources/:id/members/:user_id', 'project' do
+ let(:source) { project }
+ end
+
+ it_behaves_like 'DELETE /:sources/:id/members/:user_id', 'group' do
+ let(:source) { group }
+ end
+
+ context 'Adding owner to project' do
+ it 'returns 403' do
+ expect do
+ post v3_api("/projects/#{project.id}/members", master),
+ user_id: stranger.id, access_level: Member::OWNER
+
+ expect(response).to have_http_status(422)
+ end.to change { project.members.count }.by(0)
+ end
+ end
+end
diff --git a/spec/requests/api/v3/merge_requests_spec.rb b/spec/requests/api/v3/merge_requests_spec.rb
index b94e1ef4ced..51764d1000e 100644
--- a/spec/requests/api/v3/merge_requests_spec.rb
+++ b/spec/requests/api/v3/merge_requests_spec.rb
@@ -237,7 +237,7 @@ describe API::MergeRequests, api: true do
expect(response).to have_http_status(201)
expect(json_response['title']).to eq('Test merge_request')
- expect(json_response['labels']).to eq(['label', 'label2'])
+ expect(json_response['labels']).to eq(%w(label label2))
expect(json_response['milestone']['id']).to eq(milestone.id)
expect(json_response['force_remove_source_branch']).to be_truthy
end
diff --git a/spec/requests/api/v3/notes_spec.rb b/spec/requests/api/v3/notes_spec.rb
new file mode 100644
index 00000000000..b51cb3055d5
--- /dev/null
+++ b/spec/requests/api/v3/notes_spec.rb
@@ -0,0 +1,432 @@
+require 'spec_helper'
+
+describe API::V3::Notes, api: true do
+ include ApiHelpers
+ let(:user) { create(:user) }
+ let!(:project) { create(:empty_project, :public, namespace: user.namespace) }
+ let!(:issue) { create(:issue, project: project, author: user) }
+ let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: user) }
+ let!(:snippet) { create(:project_snippet, project: project, author: user) }
+ let!(:issue_note) { create(:note, noteable: issue, project: project, author: user) }
+ let!(:merge_request_note) { create(:note, noteable: merge_request, project: project, author: user) }
+ let!(:snippet_note) { create(:note, noteable: snippet, project: project, author: user) }
+
+ # For testing the cross-reference of a private issue in a public issue
+ let(:private_user) { create(:user) }
+ let(:private_project) do
+ create(:empty_project, namespace: private_user.namespace).
+ tap { |p| p.team << [private_user, :master] }
+ end
+ let(:private_issue) { create(:issue, project: private_project) }
+
+ let(:ext_proj) { create(:empty_project, :public) }
+ let(:ext_issue) { create(:issue, project: ext_proj) }
+
+ let!(:cross_reference_note) do
+ create :note,
+ noteable: ext_issue, project: ext_proj,
+ note: "mentioned in issue #{private_issue.to_reference(ext_proj)}",
+ system: true
+ end
+
+ before { project.team << [user, :reporter] }
+
+ describe "GET /projects/:id/noteable/:noteable_id/notes" do
+ context "when noteable is an Issue" do
+ it "returns an array of issue notes" do
+ get v3_api("/projects/#{project.id}/issues/#{issue.id}/notes", user)
+
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.first['body']).to eq(issue_note.note)
+ expect(json_response.first['upvote']).to be_falsey
+ expect(json_response.first['downvote']).to be_falsey
+ end
+
+ it "returns a 404 error when issue id not found" do
+ get v3_api("/projects/#{project.id}/issues/12345/notes", user)
+
+ expect(response).to have_http_status(404)
+ end
+
+ context "and current user cannot view the notes" do
+ it "returns an empty array" do
+ get v3_api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", user)
+
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response).to be_empty
+ end
+
+ context "and issue is confidential" do
+ before { ext_issue.update_attributes(confidential: true) }
+
+ it "returns 404" do
+ get v3_api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", user)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context "and current user can view the note" do
+ it "returns an empty array" do
+ get v3_api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", private_user)
+
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.first['body']).to eq(cross_reference_note.note)
+ end
+ end
+ end
+ end
+
+ context "when noteable is a Snippet" do
+ it "returns an array of snippet notes" do
+ get v3_api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user)
+
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.first['body']).to eq(snippet_note.note)
+ end
+
+ it "returns a 404 error when snippet id not found" do
+ get v3_api("/projects/#{project.id}/snippets/42/notes", user)
+
+ expect(response).to have_http_status(404)
+ end
+
+ it "returns 404 when not authorized" do
+ get v3_api("/projects/#{project.id}/snippets/#{snippet.id}/notes", private_user)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context "when noteable is a Merge Request" do
+ it "returns an array of merge_requests notes" do
+ get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/notes", user)
+
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.first['body']).to eq(merge_request_note.note)
+ end
+
+ it "returns a 404 error if merge request id not found" do
+ get v3_api("/projects/#{project.id}/merge_requests/4444/notes", user)
+
+ expect(response).to have_http_status(404)
+ end
+
+ it "returns 404 when not authorized" do
+ get v3_api("/projects/#{project.id}/merge_requests/4444/notes", private_user)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ describe "GET /projects/:id/noteable/:noteable_id/notes/:note_id" do
+ context "when noteable is an Issue" do
+ it "returns an issue note by id" do
+ get v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{issue_note.id}", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['body']).to eq(issue_note.note)
+ end
+
+ it "returns a 404 error if issue note not found" do
+ get v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user)
+
+ expect(response).to have_http_status(404)
+ end
+
+ context "and current user cannot view the note" do
+ it "returns a 404 error" do
+ get v3_api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes/#{cross_reference_note.id}", user)
+
+ expect(response).to have_http_status(404)
+ end
+
+ context "when issue is confidential" do
+ before { issue.update_attributes(confidential: true) }
+
+ it "returns 404" do
+ get v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{issue_note.id}", private_user)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context "and current user can view the note" do
+ it "returns an issue note by id" do
+ get v3_api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes/#{cross_reference_note.id}", private_user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['body']).to eq(cross_reference_note.note)
+ end
+ end
+ end
+ end
+
+ context "when noteable is a Snippet" do
+ it "returns a snippet note by id" do
+ get v3_api("/projects/#{project.id}/snippets/#{snippet.id}/notes/#{snippet_note.id}", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['body']).to eq(snippet_note.note)
+ end
+
+ it "returns a 404 error if snippet note not found" do
+ get v3_api("/projects/#{project.id}/snippets/#{snippet.id}/notes/12345", user)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ describe "POST /projects/:id/noteable/:noteable_id/notes" do
+ context "when noteable is an Issue" do
+ it "creates a new issue note" do
+ post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: 'hi!'
+
+ expect(response).to have_http_status(201)
+ expect(json_response['body']).to eq('hi!')
+ expect(json_response['author']['username']).to eq(user.username)
+ end
+
+ it "returns a 400 bad request error if body not given" do
+ post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes", user)
+
+ expect(response).to have_http_status(400)
+ end
+
+ it "returns a 401 unauthorized error if user not authenticated" do
+ post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes"), body: 'hi!'
+
+ expect(response).to have_http_status(401)
+ end
+
+ context 'when an admin or owner makes the request' do
+ it 'accepts the creation date to be set' do
+ creation_time = 2.weeks.ago
+ post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes", user),
+ body: 'hi!', created_at: creation_time
+
+ expect(response).to have_http_status(201)
+ expect(json_response['body']).to eq('hi!')
+ expect(json_response['author']['username']).to eq(user.username)
+ expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time)
+ end
+ end
+
+ context 'when the user is posting an award emoji on an issue created by someone else' do
+ let(:issue2) { create(:issue, project: project) }
+
+ it 'returns an award emoji' do
+ post v3_api("/projects/#{project.id}/issues/#{issue2.id}/notes", user), body: ':+1:'
+
+ expect(response).to have_http_status(201)
+ expect(json_response['awardable_id']).to eq issue2.id
+ end
+ end
+
+ context 'when the user is posting an award emoji on his/her own issue' do
+ it 'creates a new issue note' do
+ post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: ':+1:'
+
+ expect(response).to have_http_status(201)
+ expect(json_response['body']).to eq(':+1:')
+ end
+ end
+ end
+
+ context "when noteable is a Snippet" do
+ it "creates a new snippet note" do
+ post v3_api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user), body: 'hi!'
+
+ expect(response).to have_http_status(201)
+ expect(json_response['body']).to eq('hi!')
+ expect(json_response['author']['username']).to eq(user.username)
+ end
+
+ it "returns a 400 bad request error if body not given" do
+ post v3_api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user)
+
+ expect(response).to have_http_status(400)
+ end
+
+ it "returns a 401 unauthorized error if user not authenticated" do
+ post v3_api("/projects/#{project.id}/snippets/#{snippet.id}/notes"), body: 'hi!'
+
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context 'when user does not have access to read the noteable' do
+ it 'responds with 404' do
+ project = create(:empty_project, :private) { |p| p.add_guest(user) }
+ issue = create(:issue, :confidential, project: project)
+
+ post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes", user),
+ body: 'Foo'
+
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context 'when user does not have access to create noteable' do
+ let(:private_issue) { create(:issue, project: create(:empty_project, :private)) }
+
+ ##
+ # We are posting to project user has access to, but we use issue id
+ # from a different project, see #15577
+ #
+ before do
+ post v3_api("/projects/#{project.id}/issues/#{private_issue.id}/notes", user),
+ body: 'Hi!'
+ end
+
+ it 'responds with resource not found error' do
+ expect(response.status).to eq 404
+ end
+
+ it 'does not create new note' do
+ expect(private_issue.notes.reload).to be_empty
+ end
+ end
+ end
+
+ describe "POST /projects/:id/noteable/:noteable_id/notes to test observer on create" do
+ it "creates an activity event when an issue note is created" do
+ expect(Event).to receive(:create)
+
+ post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: 'hi!'
+ end
+ end
+
+ describe 'PUT /projects/:id/noteable/:noteable_id/notes/:note_id' do
+ context 'when noteable is an Issue' do
+ it 'returns modified note' do
+ put v3_api("/projects/#{project.id}/issues/#{issue.id}/"\
+ "notes/#{issue_note.id}", user), body: 'Hello!'
+
+ expect(response).to have_http_status(200)
+ expect(json_response['body']).to eq('Hello!')
+ end
+
+ it 'returns a 404 error when note id not found' do
+ put v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user),
+ body: 'Hello!'
+
+ expect(response).to have_http_status(404)
+ end
+
+ it 'returns a 400 bad request error if body not given' do
+ put v3_api("/projects/#{project.id}/issues/#{issue.id}/"\
+ "notes/#{issue_note.id}", user)
+
+ expect(response).to have_http_status(400)
+ end
+ end
+
+ context 'when noteable is a Snippet' do
+ it 'returns modified note' do
+ put v3_api("/projects/#{project.id}/snippets/#{snippet.id}/"\
+ "notes/#{snippet_note.id}", user), body: 'Hello!'
+
+ expect(response).to have_http_status(200)
+ expect(json_response['body']).to eq('Hello!')
+ end
+
+ it 'returns a 404 error when note id not found' do
+ put v3_api("/projects/#{project.id}/snippets/#{snippet.id}/"\
+ "notes/12345", user), body: "Hello!"
+
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context 'when noteable is a Merge Request' do
+ it 'returns modified note' do
+ put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/"\
+ "notes/#{merge_request_note.id}", user), body: 'Hello!'
+
+ expect(response).to have_http_status(200)
+ expect(json_response['body']).to eq('Hello!')
+ end
+
+ it 'returns a 404 error when note id not found' do
+ put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/"\
+ "notes/12345", user), body: "Hello!"
+
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/noteable/:noteable_id/notes/:note_id' do
+ context 'when noteable is an Issue' do
+ it 'deletes a note' do
+ delete v3_api("/projects/#{project.id}/issues/#{issue.id}/"\
+ "notes/#{issue_note.id}", user)
+
+ expect(response).to have_http_status(200)
+ # Check if note is really deleted
+ delete v3_api("/projects/#{project.id}/issues/#{issue.id}/"\
+ "notes/#{issue_note.id}", user)
+ expect(response).to have_http_status(404)
+ end
+
+ it 'returns a 404 error when note id not found' do
+ delete v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context 'when noteable is a Snippet' do
+ it 'deletes a note' do
+ delete v3_api("/projects/#{project.id}/snippets/#{snippet.id}/"\
+ "notes/#{snippet_note.id}", user)
+
+ expect(response).to have_http_status(200)
+ # Check if note is really deleted
+ delete v3_api("/projects/#{project.id}/snippets/#{snippet.id}/"\
+ "notes/#{snippet_note.id}", user)
+ expect(response).to have_http_status(404)
+ end
+
+ it 'returns a 404 error when note id not found' do
+ delete v3_api("/projects/#{project.id}/snippets/#{snippet.id}/"\
+ "notes/12345", user)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context 'when noteable is a Merge Request' do
+ it 'deletes a note' do
+ delete v3_api("/projects/#{project.id}/merge_requests/"\
+ "#{merge_request.id}/notes/#{merge_request_note.id}", user)
+
+ expect(response).to have_http_status(200)
+ # Check if note is really deleted
+ delete v3_api("/projects/#{project.id}/merge_requests/"\
+ "#{merge_request.id}/notes/#{merge_request_note.id}", user)
+ expect(response).to have_http_status(404)
+ end
+
+ it 'returns a 404 error when note id not found' do
+ delete v3_api("/projects/#{project.id}/merge_requests/"\
+ "#{merge_request.id}/notes/12345", user)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/v3/project_snippets_spec.rb b/spec/requests/api/v3/project_snippets_spec.rb
index 3700477f0db..957a3bf97ef 100644
--- a/spec/requests/api/v3/project_snippets_spec.rb
+++ b/spec/requests/api/v3/project_snippets_spec.rb
@@ -85,43 +85,33 @@ describe API::ProjectSnippets, api: true do
allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
end
- context 'when the project is private' do
- let(:private_project) { create(:project_empty_repo, :private) }
-
- context 'when the snippet is public' do
- it 'creates the snippet' do
- expect { create_snippet(private_project, visibility_level: Snippet::PUBLIC) }.
- to change { Snippet.count }.by(1)
- end
+ context 'when the snippet is private' do
+ it 'creates the snippet' do
+ expect { create_snippet(project, visibility_level: Snippet::PRIVATE) }.
+ to change { Snippet.count }.by(1)
end
end
- context 'when the project is public' do
- context 'when the snippet is private' do
- it 'creates the snippet' do
- expect { create_snippet(project, visibility_level: Snippet::PRIVATE) }.
- to change { Snippet.count }.by(1)
- end
+ context 'when the snippet is public' do
+ it 'rejects the shippet' do
+ expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
+ not_to change { Snippet.count }
+
+ expect(response).to have_http_status(400)
+ expect(json_response['message']).to eq({ "error" => "Spam detected" })
end
- context 'when the snippet is public' do
- it 'rejects the shippet' do
- expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
- not_to change { Snippet.count }
- expect(response).to have_http_status(400)
- end
-
- it 'creates a spam log' do
- expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
- to change { SpamLog.count }.by(1)
- end
+ it 'creates a spam log' do
+ expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
+ to change { SpamLog.count }.by(1)
end
end
end
end
describe 'PUT /projects/:project_id/snippets/:id/' do
- let(:snippet) { create(:project_snippet, author: admin) }
+ let(:visibility_level) { Snippet::PUBLIC }
+ let(:snippet) { create(:project_snippet, author: admin, visibility_level: visibility_level) }
it 'updates snippet' do
new_content = 'New content'
@@ -145,6 +135,56 @@ describe API::ProjectSnippets, api: true do
expect(response).to have_http_status(400)
end
+
+ context 'when the snippet is spam' do
+ def update_snippet(snippet_params = {})
+ put v3_api("/projects/#{snippet.project.id}/snippets/#{snippet.id}", admin), snippet_params
+ end
+
+ before do
+ allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
+ end
+
+ context 'when the snippet is private' do
+ let(:visibility_level) { Snippet::PRIVATE }
+
+ it 'creates the snippet' do
+ expect { update_snippet(title: 'Foo') }.
+ to change { snippet.reload.title }.to('Foo')
+ end
+ end
+
+ context 'when the snippet is public' do
+ let(:visibility_level) { Snippet::PUBLIC }
+
+ it 'rejects the snippet' do
+ expect { update_snippet(title: 'Foo') }.
+ not_to change { snippet.reload.title }
+ end
+
+ it 'creates a spam log' do
+ expect { update_snippet(title: 'Foo') }.
+ to change { SpamLog.count }.by(1)
+ end
+ end
+
+ context 'when the private snippet is made public' do
+ let(:visibility_level) { Snippet::PRIVATE }
+
+ it 'rejects the snippet' do
+ expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }.
+ not_to change { snippet.reload.title }
+
+ expect(response).to have_http_status(400)
+ expect(json_response['message']).to eq({ "error" => "Spam detected" })
+ end
+
+ it 'creates a spam log' do
+ expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }.
+ to change { SpamLog.count }.by(1)
+ end
+ end
+ end
end
describe 'DELETE /projects/:project_id/snippets/:id/' do
diff --git a/spec/requests/api/v3/projects_spec.rb b/spec/requests/api/v3/projects_spec.rb
index a495122bba7..662be3f3531 100644
--- a/spec/requests/api/v3/projects_spec.rb
+++ b/spec/requests/api/v3/projects_spec.rb
@@ -84,7 +84,7 @@ describe API::V3::Projects, api: true do
context 'GET /projects?simple=true' do
it 'returns a simplified version of all the projects' do
- expected_keys = ["id", "http_url_to_repo", "web_url", "name", "name_with_namespace", "path", "path_with_namespace"]
+ expected_keys = %w(id http_url_to_repo web_url name name_with_namespace path path_with_namespace)
get v3_api('/projects?simple=true', user)
@@ -682,6 +682,7 @@ describe API::V3::Projects, api: true do
'name' => user.namespace.name,
'path' => user.namespace.path,
'kind' => user.namespace.kind,
+ 'full_path' => user.namespace.full_path,
})
end
diff --git a/spec/requests/api/v3/repositories_spec.rb b/spec/requests/api/v3/repositories_spec.rb
new file mode 100644
index 00000000000..c696721c1c9
--- /dev/null
+++ b/spec/requests/api/v3/repositories_spec.rb
@@ -0,0 +1,144 @@
+require 'spec_helper'
+require 'mime/types'
+
+describe API::V3::Repositories, api: true do
+ include ApiHelpers
+
+ let(:user) { create(:user) }
+ let(:guest) { create(:user).tap { |u| create(:project_member, :guest, user: u, project: project) } }
+ let!(:project) { create(:project, :repository, creator: user) }
+ let!(:master) { create(:project_member, :master, user: user, project: project) }
+
+ describe "GET /projects/:id/repository/tree" do
+ let(:route) { "/projects/#{project.id}/repository/tree" }
+
+ shared_examples_for 'repository tree' do
+ it 'returns the repository tree' do
+ get v3_api(route, current_user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+
+ first_commit = json_response.first
+ expect(first_commit['name']).to eq('bar')
+ expect(first_commit['type']).to eq('tree')
+ expect(first_commit['mode']).to eq('040000')
+ end
+
+ context 'when ref does not exist' do
+ it_behaves_like '404 response' do
+ let(:request) { get v3_api("#{route}?ref_name=foo", current_user) }
+ let(:message) { '404 Tree Not Found' }
+ end
+ end
+
+ context 'when repository is disabled' do
+ include_context 'disabled repository'
+
+ it_behaves_like '403 response' do
+ let(:request) { get v3_api(route, current_user) }
+ end
+ end
+
+ context 'with recursive=1' do
+ it 'returns recursive project paths tree' do
+ get v3_api("#{route}?recursive=1", current_user)
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response[4]['name']).to eq('html')
+ expect(json_response[4]['path']).to eq('files/html')
+ expect(json_response[4]['type']).to eq('tree')
+ expect(json_response[4]['mode']).to eq('040000')
+ end
+
+ context 'when repository is disabled' do
+ include_context 'disabled repository'
+
+ it_behaves_like '403 response' do
+ let(:request) { get v3_api(route, current_user) }
+ end
+ end
+
+ context 'when ref does not exist' do
+ it_behaves_like '404 response' do
+ let(:request) { get v3_api("#{route}?recursive=1&ref_name=foo", current_user) }
+ let(:message) { '404 Tree Not Found' }
+ end
+ end
+ end
+ end
+
+ context 'when unauthenticated', 'and project is public' do
+ it_behaves_like 'repository tree' do
+ let(:project) { create(:project, :public, :repository) }
+ let(:current_user) { nil }
+ end
+ end
+
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { get v3_api(route) }
+ let(:message) { '404 Project Not Found' }
+ end
+ end
+
+ context 'when authenticated', 'as a developer' do
+ it_behaves_like 'repository tree' do
+ let(:current_user) { user }
+ end
+ end
+
+ context 'when authenticated', 'as a guest' do
+ it_behaves_like '403 response' do
+ let(:request) { get v3_api(route, guest) }
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/repository/contributors' do
+ let(:route) { "/projects/#{project.id}/repository/contributors" }
+
+ shared_examples_for 'repository contributors' do
+ it 'returns valid data' do
+ get v3_api(route, current_user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+
+ first_contributor = json_response.first
+ expect(first_contributor['email']).to eq('tiagonbotelho@hotmail.com')
+ expect(first_contributor['name']).to eq('tiagonbotelho')
+ expect(first_contributor['commits']).to eq(1)
+ expect(first_contributor['additions']).to eq(0)
+ expect(first_contributor['deletions']).to eq(0)
+ end
+ end
+
+ context 'when unauthenticated', 'and project is public' do
+ it_behaves_like 'repository contributors' do
+ let(:project) { create(:project, :public, :repository) }
+ let(:current_user) { nil }
+ end
+ end
+
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { get v3_api(route) }
+ let(:message) { '404 Project Not Found' }
+ end
+ end
+
+ context 'when authenticated', 'as a developer' do
+ it_behaves_like 'repository contributors' do
+ let(:current_user) { user }
+ end
+ end
+
+ context 'when authenticated', 'as a guest' do
+ it_behaves_like '403 response' do
+ let(:request) { get v3_api(route, guest) }
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/v3/system_hooks_spec.rb b/spec/requests/api/v3/system_hooks_spec.rb
new file mode 100644
index 00000000000..da58efb6ebf
--- /dev/null
+++ b/spec/requests/api/v3/system_hooks_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+describe API::V3::SystemHooks, api: true do
+ include ApiHelpers
+
+ let(:user) { create(:user) }
+ let(:admin) { create(:admin) }
+ let!(:hook) { create(:system_hook, url: "http://example.com") }
+
+ before { stub_request(:post, hook.url) }
+
+ describe "GET /hooks" do
+ context "when no user" do
+ it "returns authentication error" do
+ get v3_api("/hooks")
+
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context "when not an admin" do
+ it "returns forbidden error" do
+ get v3_api("/hooks", user)
+
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ context "when authenticated as admin" do
+ it "returns an array of hooks" do
+ get v3_api("/hooks", admin)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['url']).to eq(hook.url)
+ expect(json_response.first['push_events']).to be true
+ expect(json_response.first['tag_push_events']).to be false
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/v3/tags_spec.rb b/spec/requests/api/v3/tags_spec.rb
new file mode 100644
index 00000000000..6722789d928
--- /dev/null
+++ b/spec/requests/api/v3/tags_spec.rb
@@ -0,0 +1,67 @@
+require 'spec_helper'
+require 'mime/types'
+
+describe API::V3::Tags, api: true do
+ include ApiHelpers
+ include RepoHelpers
+
+ let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+ let!(:project) { create(:project, :repository, creator: user) }
+ let!(:master) { create(:project_member, :master, user: user, project: project) }
+
+ describe "GET /projects/:id/repository/tags" do
+ let(:tag_name) { project.repository.tag_names.sort.reverse.first }
+ let(:description) { 'Awesome release!' }
+
+ shared_examples_for 'repository tags' do
+ it 'returns the repository tags' do
+ get v3_api("/projects/#{project.id}/repository/tags", current_user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['name']).to eq(tag_name)
+ end
+ end
+
+ context 'when unauthenticated' do
+ it_behaves_like 'repository tags' do
+ let(:project) { create(:project, :public, :repository) }
+ let(:current_user) { nil }
+ end
+ end
+
+ context 'when authenticated' do
+ it_behaves_like 'repository tags' do
+ let(:current_user) { user }
+ end
+ end
+
+ context 'without releases' do
+ it "returns an array of project tags" do
+ get v3_api("/projects/#{project.id}/repository/tags", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['name']).to eq(tag_name)
+ end
+ end
+
+ context 'with releases' do
+ before do
+ release = project.releases.find_or_initialize_by(tag: tag_name)
+ release.update_attributes(description: description)
+ end
+
+ it "returns an array of project tags with release info" do
+ get v3_api("/projects/#{project.id}/repository/tags", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['name']).to eq(tag_name)
+ expect(json_response.first['message']).to eq('Version 1.1.0')
+ expect(json_response.first['release']['description']).to eq(description)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/v3/templates_spec.rb b/spec/requests/api/v3/templates_spec.rb
new file mode 100644
index 00000000000..f1e554b98cc
--- /dev/null
+++ b/spec/requests/api/v3/templates_spec.rb
@@ -0,0 +1,203 @@
+require 'spec_helper'
+
+describe API::V3::Templates, api: true do
+ include ApiHelpers
+
+ shared_examples_for 'the Template Entity' do |path|
+ before { get v3_api(path) }
+
+ it { expect(json_response['name']).to eq('Ruby') }
+ it { expect(json_response['content']).to include('*.gem') }
+ end
+
+ shared_examples_for 'the TemplateList Entity' do |path|
+ before { get v3_api(path) }
+
+ it { expect(json_response.first['name']).not_to be_nil }
+ it { expect(json_response.first['content']).to be_nil }
+ end
+
+ shared_examples_for 'requesting gitignores' do |path|
+ it 'returns a list of available gitignore templates' do
+ get v3_api(path)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to be > 15
+ end
+ end
+
+ shared_examples_for 'requesting gitlab-ci-ymls' do |path|
+ it 'returns a list of available gitlab_ci_ymls' do
+ get v3_api(path)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['name']).not_to be_nil
+ end
+ end
+
+ shared_examples_for 'requesting gitlab-ci-yml for Ruby' do |path|
+ it 'adds a disclaimer on the top' do
+ get v3_api(path)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['content']).to start_with("# This file is a template,")
+ end
+ end
+
+ shared_examples_for 'the License Template Entity' do |path|
+ before { get v3_api(path) }
+
+ it 'returns a license template' do
+ expect(json_response['key']).to eq('mit')
+ expect(json_response['name']).to eq('MIT License')
+ expect(json_response['nickname']).to be_nil
+ expect(json_response['popular']).to be true
+ expect(json_response['html_url']).to eq('http://choosealicense.com/licenses/mit/')
+ expect(json_response['source_url']).to eq('https://opensource.org/licenses/MIT')
+ expect(json_response['description']).to include('A short and simple permissive license with conditions')
+ expect(json_response['conditions']).to eq(%w[include-copyright])
+ expect(json_response['permissions']).to eq(%w[commercial-use modifications distribution private-use])
+ expect(json_response['limitations']).to eq(%w[no-liability])
+ expect(json_response['content']).to include('MIT License')
+ end
+ end
+
+ shared_examples_for 'GET licenses' do |path|
+ it 'returns a list of available license templates' do
+ get v3_api(path)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(12)
+ expect(json_response.map { |l| l['key'] }).to include('agpl-3.0')
+ end
+
+ describe 'the popular parameter' do
+ context 'with popular=1' do
+ it 'returns a list of available popular license templates' do
+ get v3_api("#{path}?popular=1")
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(3)
+ expect(json_response.map { |l| l['key'] }).to include('apache-2.0')
+ end
+ end
+ end
+ end
+
+ shared_examples_for 'GET licenses/:name' do |path|
+ context 'with :project and :fullname given' do
+ before do
+ get v3_api("#{path}/#{license_type}?project=My+Awesome+Project&fullname=Anton+#{license_type.upcase}")
+ end
+
+ context 'for the mit license' do
+ let(:license_type) { 'mit' }
+
+ it 'returns the license text' do
+ expect(json_response['content']).to include('MIT License')
+ end
+
+ it 'replaces placeholder values' do
+ expect(json_response['content']).to include("Copyright (c) #{Time.now.year} Anton")
+ end
+ end
+
+ context 'for the agpl-3.0 license' do
+ let(:license_type) { 'agpl-3.0' }
+
+ it 'returns the license text' do
+ expect(json_response['content']).to include('GNU AFFERO GENERAL PUBLIC LICENSE')
+ end
+
+ it 'replaces placeholder values' do
+ expect(json_response['content']).to include('My Awesome Project')
+ expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton")
+ end
+ end
+
+ context 'for the gpl-3.0 license' do
+ let(:license_type) { 'gpl-3.0' }
+
+ it 'returns the license text' do
+ expect(json_response['content']).to include('GNU GENERAL PUBLIC LICENSE')
+ end
+
+ it 'replaces placeholder values' do
+ expect(json_response['content']).to include('My Awesome Project')
+ expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton")
+ end
+ end
+
+ context 'for the gpl-2.0 license' do
+ let(:license_type) { 'gpl-2.0' }
+
+ it 'returns the license text' do
+ expect(json_response['content']).to include('GNU GENERAL PUBLIC LICENSE')
+ end
+
+ it 'replaces placeholder values' do
+ expect(json_response['content']).to include('My Awesome Project')
+ expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton")
+ end
+ end
+
+ context 'for the apache-2.0 license' do
+ let(:license_type) { 'apache-2.0' }
+
+ it 'returns the license text' do
+ expect(json_response['content']).to include('Apache License')
+ end
+
+ it 'replaces placeholder values' do
+ expect(json_response['content']).to include("Copyright #{Time.now.year} Anton")
+ end
+ end
+
+ context 'for an uknown license' do
+ let(:license_type) { 'muth-over9000' }
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ context 'with no :fullname given' do
+ context 'with an authenticated user' do
+ let(:user) { create(:user) }
+
+ it 'replaces the copyright owner placeholder with the name of the current user' do
+ get v3_api('/templates/licenses/mit', user)
+
+ expect(json_response['content']).to include("Copyright (c) #{Time.now.year} #{user.name}")
+ end
+ end
+ end
+ end
+
+ describe 'with /templates namespace' do
+ it_behaves_like 'the Template Entity', '/templates/gitignores/Ruby'
+ it_behaves_like 'the TemplateList Entity', '/templates/gitignores'
+ it_behaves_like 'requesting gitignores', '/templates/gitignores'
+ it_behaves_like 'requesting gitlab-ci-ymls', '/templates/gitlab_ci_ymls'
+ it_behaves_like 'requesting gitlab-ci-yml for Ruby', '/templates/gitlab_ci_ymls/Ruby'
+ it_behaves_like 'the License Template Entity', '/templates/licenses/mit'
+ it_behaves_like 'GET licenses', '/templates/licenses'
+ it_behaves_like 'GET licenses/:name', '/templates/licenses'
+ end
+
+ describe 'without /templates namespace' do
+ it_behaves_like 'the Template Entity', '/gitignores/Ruby'
+ it_behaves_like 'the TemplateList Entity', '/gitignores'
+ it_behaves_like 'requesting gitignores', '/gitignores'
+ it_behaves_like 'requesting gitlab-ci-ymls', '/gitlab_ci_ymls'
+ it_behaves_like 'requesting gitlab-ci-yml for Ruby', '/gitlab_ci_ymls/Ruby'
+ it_behaves_like 'the License Template Entity', '/licenses/mit'
+ it_behaves_like 'GET licenses', '/licenses'
+ it_behaves_like 'GET licenses/:name', '/licenses'
+ end
+end
diff --git a/spec/requests/api/v3/todos_spec.rb b/spec/requests/api/v3/todos_spec.rb
new file mode 100644
index 00000000000..80fa697e949
--- /dev/null
+++ b/spec/requests/api/v3/todos_spec.rb
@@ -0,0 +1,73 @@
+require 'spec_helper'
+
+describe API::V3::Todos, api: true do
+ include ApiHelpers
+
+ let(:project_1) { create(:empty_project) }
+ let(:project_2) { create(:empty_project) }
+ let(:author_1) { create(:user) }
+ let(:author_2) { create(:user) }
+ let(:john_doe) { create(:user, username: 'john_doe') }
+ let!(:pending_1) { create(:todo, :mentioned, project: project_1, author: author_1, user: john_doe) }
+ let!(:pending_2) { create(:todo, project: project_2, author: author_2, user: john_doe) }
+ let!(:pending_3) { create(:todo, project: project_1, author: author_2, user: john_doe) }
+ let!(:done) { create(:todo, :done, project: project_1, author: author_1, user: john_doe) }
+
+ before do
+ project_1.team << [john_doe, :developer]
+ project_2.team << [john_doe, :developer]
+ end
+
+ describe 'DELETE /todos/:id' do
+ context 'when unauthenticated' do
+ it 'returns authentication error' do
+ delete v3_api("/todos/#{pending_1.id}")
+
+ expect(response.status).to eq(401)
+ end
+ end
+
+ context 'when authenticated' do
+ it 'marks a todo as done' do
+ delete v3_api("/todos/#{pending_1.id}", john_doe)
+
+ expect(response.status).to eq(200)
+ expect(pending_1.reload).to be_done
+ end
+
+ it 'updates todos cache' do
+ expect_any_instance_of(User).to receive(:update_todos_count_cache).and_call_original
+
+ delete v3_api("/todos/#{pending_1.id}", john_doe)
+ end
+ end
+ end
+
+ describe 'DELETE /todos' do
+ context 'when unauthenticated' do
+ it 'returns authentication error' do
+ delete v3_api('/todos')
+
+ expect(response.status).to eq(401)
+ end
+ end
+
+ context 'when authenticated' do
+ it 'marks all todos as done' do
+ delete v3_api('/todos', john_doe)
+
+ expect(response.status).to eq(200)
+ expect(response.body).to eq('3')
+ expect(pending_1.reload).to be_done
+ expect(pending_2.reload).to be_done
+ expect(pending_3.reload).to be_done
+ end
+
+ it 'updates todos cache' do
+ expect_any_instance_of(User).to receive(:update_todos_count_cache).and_call_original
+
+ delete v3_api("/todos", john_doe)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/v3/users_spec.rb b/spec/requests/api/v3/users_spec.rb
new file mode 100644
index 00000000000..17bbb0b53c1
--- /dev/null
+++ b/spec/requests/api/v3/users_spec.rb
@@ -0,0 +1,266 @@
+require 'spec_helper'
+
+describe API::V3::Users, api: true do
+ include ApiHelpers
+
+ let(:user) { create(:user) }
+ let(:admin) { create(:admin) }
+ let(:key) { create(:key, user: user) }
+ let(:email) { create(:email, user: user) }
+ let(:ldap_blocked_user) { create(:omniauth_user, provider: 'ldapmain', state: 'ldap_blocked') }
+
+ describe 'GET /user/:id/keys' do
+ before { admin }
+
+ context 'when unauthenticated' do
+ it 'returns authentication error' do
+ get v3_api("/users/#{user.id}/keys")
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context 'when authenticated' do
+ it 'returns 404 for non-existing user' do
+ get v3_api('/users/999999/keys', admin)
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 User Not Found')
+ end
+
+ it 'returns array of ssh keys' do
+ user.keys << key
+ user.save
+
+ get v3_api("/users/#{user.id}/keys", admin)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['title']).to eq(key.title)
+ end
+ end
+ end
+
+ describe 'GET /user/:id/emails' do
+ before { admin }
+
+ context 'when unauthenticated' do
+ it 'returns authentication error' do
+ get v3_api("/users/#{user.id}/emails")
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context 'when authenticated' do
+ it 'returns 404 for non-existing user' do
+ get v3_api('/users/999999/emails', admin)
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 User Not Found')
+ end
+
+ it 'returns array of emails' do
+ user.emails << email
+ user.save
+
+ get v3_api("/users/#{user.id}/emails", admin)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['email']).to eq(email.email)
+ end
+
+ it "returns a 404 for invalid ID" do
+ put v3_api("/users/ASDF/emails", admin)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ describe "GET /user/keys" do
+ context "when unauthenticated" do
+ it "returns authentication error" do
+ get v3_api("/user/keys")
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context "when authenticated" do
+ it "returns array of ssh keys" do
+ user.keys << key
+ user.save
+
+ get v3_api("/user/keys", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first["title"]).to eq(key.title)
+ end
+ end
+ end
+
+ describe "GET /user/emails" do
+ context "when unauthenticated" do
+ it "returns authentication error" do
+ get v3_api("/user/emails")
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context "when authenticated" do
+ it "returns array of emails" do
+ user.emails << email
+ user.save
+
+ get v3_api("/user/emails", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first["email"]).to eq(email.email)
+ end
+ end
+ end
+
+ describe 'PUT /users/:id/block' do
+ before { admin }
+ it 'blocks existing user' do
+ put v3_api("/users/#{user.id}/block", admin)
+ expect(response).to have_http_status(200)
+ expect(user.reload.state).to eq('blocked')
+ end
+
+ it 'does not re-block ldap blocked users' do
+ put v3_api("/users/#{ldap_blocked_user.id}/block", admin)
+ expect(response).to have_http_status(403)
+ expect(ldap_blocked_user.reload.state).to eq('ldap_blocked')
+ end
+
+ it 'does not be available for non admin users' do
+ put v3_api("/users/#{user.id}/block", user)
+ expect(response).to have_http_status(403)
+ expect(user.reload.state).to eq('active')
+ end
+
+ it 'returns a 404 error if user id not found' do
+ put v3_api('/users/9999/block', admin)
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 User Not Found')
+ end
+ end
+
+ describe 'PUT /users/:id/unblock' do
+ let(:blocked_user) { create(:user, state: 'blocked') }
+ before { admin }
+
+ it 'unblocks existing user' do
+ put v3_api("/users/#{user.id}/unblock", admin)
+ expect(response).to have_http_status(200)
+ expect(user.reload.state).to eq('active')
+ end
+
+ it 'unblocks a blocked user' do
+ put v3_api("/users/#{blocked_user.id}/unblock", admin)
+ expect(response).to have_http_status(200)
+ expect(blocked_user.reload.state).to eq('active')
+ end
+
+ it 'does not unblock ldap blocked users' do
+ put v3_api("/users/#{ldap_blocked_user.id}/unblock", admin)
+ expect(response).to have_http_status(403)
+ expect(ldap_blocked_user.reload.state).to eq('ldap_blocked')
+ end
+
+ it 'does not be available for non admin users' do
+ put v3_api("/users/#{user.id}/unblock", user)
+ expect(response).to have_http_status(403)
+ expect(user.reload.state).to eq('active')
+ end
+
+ it 'returns a 404 error if user id not found' do
+ put v3_api('/users/9999/block', admin)
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 User Not Found')
+ end
+
+ it "returns a 404 for invalid ID" do
+ put v3_api("/users/ASDF/block", admin)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ describe 'GET /users/:id/events' do
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project) }
+ let(:note) { create(:note_on_issue, note: 'What an awesome day!', project: project) }
+
+ before do
+ project.add_user(user, :developer)
+ EventCreateService.new.leave_note(note, user)
+ end
+
+ context "as a user than cannot see the event's project" do
+ it 'returns no events' do
+ other_user = create(:user)
+
+ get api("/users/#{user.id}/events", other_user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_empty
+ end
+ end
+
+ context "as a user than can see the event's project" do
+ context 'joined event' do
+ it 'returns the "joined" event' do
+ get v3_api("/users/#{user.id}/events", user)
+
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+
+ comment_event = json_response.find { |e| e['action_name'] == 'commented on' }
+
+ expect(comment_event['project_id'].to_i).to eq(project.id)
+ expect(comment_event['author_username']).to eq(user.username)
+ expect(comment_event['note']['id']).to eq(note.id)
+ expect(comment_event['note']['body']).to eq('What an awesome day!')
+
+ joined_event = json_response.find { |e| e['action_name'] == 'joined' }
+
+ expect(joined_event['project_id'].to_i).to eq(project.id)
+ expect(joined_event['author_username']).to eq(user.username)
+ expect(joined_event['author']['name']).to eq(user.name)
+ end
+ end
+
+ context 'when there are multiple events from different projects' do
+ let(:second_note) { create(:note_on_issue, project: create(:empty_project)) }
+ let(:third_note) { create(:note_on_issue, project: project) }
+
+ before do
+ second_note.project.add_user(user, :developer)
+
+ [second_note, third_note].each do |note|
+ EventCreateService.new.leave_note(note, user)
+ end
+ end
+
+ it 'returns events in the correct order (from newest to oldest)' do
+ get v3_api("/users/#{user.id}/events", user)
+
+ comment_events = json_response.select { |e| e['action_name'] == 'commented on' }
+
+ expect(comment_events[0]['target_id']).to eq(third_note.id)
+ expect(comment_events[1]['target_id']).to eq(second_note.id)
+ expect(comment_events[2]['target_id']).to eq(note.id)
+ end
+ end
+ end
+
+ it 'returns a 404 error if not found' do
+ get v3_api('/users/42/events', user)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 User Not Found')
+ end
+ end
+end
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index d85afdeab42..9948d1a9ea0 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe Ci::API::Builds do
include ApiHelpers
- let(:runner) { FactoryGirl.create(:ci_runner, tag_list: ["mysql", "ruby"]) }
+ let(:runner) { FactoryGirl.create(:ci_runner, tag_list: %w(mysql ruby)) }
let(:project) { FactoryGirl.create(:empty_project, shared_runners_enabled: false) }
let(:last_update) { nil }
@@ -630,6 +630,7 @@ describe Ci::API::Builds do
context 'with an expire date' do
let!(:artifacts) { file_upload }
+ let(:default_artifacts_expire_in) {}
let(:post_data) do
{ 'file.path' => artifacts.path,
@@ -638,6 +639,9 @@ describe Ci::API::Builds do
end
before do
+ stub_application_setting(
+ default_artifacts_expire_in: default_artifacts_expire_in)
+
post(post_url, post_data, headers_with_token)
end
@@ -648,7 +652,8 @@ describe Ci::API::Builds do
build.reload
expect(response).to have_http_status(201)
expect(json_response['artifacts_expire_at']).not_to be_empty
- expect(build.artifacts_expire_at).to be_within(5.minutes).of(Time.now + 7.days)
+ expect(build.artifacts_expire_at).
+ to be_within(5.minutes).of(7.days.from_now)
end
end
@@ -661,6 +666,32 @@ describe Ci::API::Builds do
expect(json_response['artifacts_expire_at']).to be_nil
expect(build.artifacts_expire_at).to be_nil
end
+
+ context 'with application default' do
+ context 'default to 5 days' do
+ let(:default_artifacts_expire_in) { '5 days' }
+
+ it 'sets to application default' do
+ build.reload
+ expect(response).to have_http_status(201)
+ expect(json_response['artifacts_expire_at'])
+ .not_to be_empty
+ expect(build.artifacts_expire_at)
+ .to be_within(5.minutes).of(5.days.from_now)
+ end
+ end
+
+ context 'default to 0' do
+ let(:default_artifacts_expire_in) { '0' }
+
+ it 'does not set expire_in' do
+ build.reload
+ expect(response).to have_http_status(201)
+ expect(json_response['artifacts_expire_at']).to be_nil
+ expect(build.artifacts_expire_at).to be_nil
+ end
+ end
+ end
end
end
diff --git a/spec/requests/ci/api/runners_spec.rb b/spec/requests/ci/api/runners_spec.rb
index bd55934d0c8..8719313783e 100644
--- a/spec/requests/ci/api/runners_spec.rb
+++ b/spec/requests/ci/api/runners_spec.rb
@@ -41,7 +41,7 @@ describe Ci::API::Runners do
it 'creates runner' do
expect(response).to have_http_status 201
- expect(Ci::Runner.first.tag_list.sort).to eq(["tag1", "tag2"])
+ expect(Ci::Runner.first.tag_list.sort).to eq(%w(tag1 tag2))
end
end
diff --git a/spec/requests/ci/api/triggers_spec.rb b/spec/requests/ci/api/triggers_spec.rb
index a30be767119..5321f8b134f 100644
--- a/spec/requests/ci/api/triggers_spec.rb
+++ b/spec/requests/ci/api/triggers_spec.rb
@@ -60,7 +60,8 @@ describe Ci::API::Triggers do
it 'validates variables to be a hash' do
post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options.merge(variables: 'value')
expect(response).to have_http_status(400)
- expect(json_response['message']).to eq('variables needs to be a hash')
+
+ expect(json_response['error']).to eq('variables is invalid')
end
it 'validates variables needs to be a map of key-valued strings' do
diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb
index 9bfc84c7425..5d495bc9e7d 100644
--- a/spec/requests/lfs_http_spec.rb
+++ b/spec/requests/lfs_http_spec.rb
@@ -25,11 +25,9 @@ describe 'Git LFS API and storage' do
{
'objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
- 'size' => 1575078
- },
+ 'size' => 1575078 },
{ 'oid' => sample_oid,
- 'size' => sample_size
- }
+ 'size' => sample_size }
],
'operation' => 'upload'
}
@@ -53,11 +51,9 @@ describe 'Git LFS API and storage' do
{
'objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
- 'size' => 1575078
- },
+ 'size' => 1575078 },
{ 'oid' => sample_oid,
- 'size' => sample_size
- }
+ 'size' => sample_size }
],
'operation' => 'upload'
}
@@ -374,11 +370,12 @@ describe 'Git LFS API and storage' do
describe 'download' do
let(:project) { create(:empty_project) }
let(:body) do
- { 'operation' => 'download',
+ {
+ 'operation' => 'download',
'objects' => [
{ 'oid' => sample_oid,
- 'size' => sample_size
- }]
+ 'size' => sample_size }
+ ]
}
end
@@ -393,16 +390,20 @@ describe 'Git LFS API and storage' do
end
it 'with href to download' do
- expect(json_response).to eq('objects' => [
- { 'oid' => sample_oid,
- 'size' => sample_size,
- 'actions' => {
- 'download' => {
- 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
- 'header' => { 'Authorization' => authorization }
+ expect(json_response).to eq({
+ 'objects' => [
+ {
+ 'oid' => sample_oid,
+ 'size' => sample_size,
+ 'actions' => {
+ 'download' => {
+ 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
+ 'header' => { 'Authorization' => authorization }
+ }
}
}
- }])
+ ]
+ })
end
end
@@ -417,24 +418,29 @@ describe 'Git LFS API and storage' do
end
it 'with href to download' do
- expect(json_response).to eq('objects' => [
- { 'oid' => sample_oid,
- 'size' => sample_size,
- 'error' => {
- 'code' => 404,
- 'message' => "Object does not exist on the server or you don't have permissions to access it",
+ expect(json_response).to eq({
+ 'objects' => [
+ {
+ 'oid' => sample_oid,
+ 'size' => sample_size,
+ 'error' => {
+ 'code' => 404,
+ 'message' => "Object does not exist on the server or you don't have permissions to access it",
+ }
}
- }])
+ ]
+ })
end
end
context 'when downloading a lfs object that does not exist' do
let(:body) do
- { 'operation' => 'download',
+ {
+ 'operation' => 'download',
'objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
- 'size' => 1575078
- }]
+ 'size' => 1575078 }
+ ]
}
end
@@ -443,27 +449,30 @@ describe 'Git LFS API and storage' do
end
it 'with an 404 for specific object' do
- expect(json_response).to eq('objects' => [
- { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
- 'size' => 1575078,
- 'error' => {
- 'code' => 404,
- 'message' => "Object does not exist on the server or you don't have permissions to access it",
+ expect(json_response).to eq({
+ 'objects' => [
+ {
+ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
+ 'size' => 1575078,
+ 'error' => {
+ 'code' => 404,
+ 'message' => "Object does not exist on the server or you don't have permissions to access it",
+ }
}
- }])
+ ]
+ })
end
end
context 'when downloading one new and one existing lfs object' do
let(:body) do
- { 'operation' => 'download',
+ {
+ 'operation' => 'download',
'objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
- 'size' => 1575078
- },
+ 'size' => 1575078 },
{ 'oid' => sample_oid,
- 'size' => sample_size
- }
+ 'size' => sample_size }
]
}
end
@@ -477,23 +486,28 @@ describe 'Git LFS API and storage' do
end
it 'responds with upload hypermedia link for the new object' do
- expect(json_response).to eq('objects' => [
- { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
- 'size' => 1575078,
- 'error' => {
- 'code' => 404,
- 'message' => "Object does not exist on the server or you don't have permissions to access it",
- }
- },
- { 'oid' => sample_oid,
- 'size' => sample_size,
- 'actions' => {
- 'download' => {
- 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
- 'header' => { 'Authorization' => authorization }
+ expect(json_response).to eq({
+ 'objects' => [
+ {
+ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
+ 'size' => 1575078,
+ 'error' => {
+ 'code' => 404,
+ 'message' => "Object does not exist on the server or you don't have permissions to access it",
+ }
+ },
+ {
+ 'oid' => sample_oid,
+ 'size' => sample_size,
+ 'actions' => {
+ 'download' => {
+ 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
+ 'header' => { 'Authorization' => authorization }
+ }
}
}
- }])
+ ]
+ })
end
end
end
@@ -597,16 +611,21 @@ describe 'Git LFS API and storage' do
end
it 'responds with status 200 and href to download' do
- expect(json_response).to eq('objects' => [
- { 'oid' => sample_oid,
- 'size' => sample_size,
- 'actions' => {
- 'download' => {
- 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
- 'header' => {}
+ expect(json_response).to eq({
+ 'objects' => [
+ {
+ 'oid' => sample_oid,
+ 'size' => sample_size,
+ 'authenticated' => true,
+ 'actions' => {
+ 'download' => {
+ 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
+ 'header' => {}
+ }
}
}
- }])
+ ]
+ })
end
end
@@ -625,11 +644,12 @@ describe 'Git LFS API and storage' do
describe 'upload' do
let(:project) { create(:project, :public) }
let(:body) do
- { 'operation' => 'upload',
+ {
+ 'operation' => 'upload',
'objects' => [
{ 'oid' => sample_oid,
- 'size' => sample_size
- }]
+ 'size' => sample_size }
+ ]
}
end
@@ -664,11 +684,12 @@ describe 'Git LFS API and storage' do
context 'when pushing a lfs object that does not exist' do
let(:body) do
- { 'operation' => 'upload',
+ {
+ 'operation' => 'upload',
'objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
- 'size' => 1575078
- }]
+ 'size' => 1575078 }
+ ]
}
end
@@ -687,14 +708,13 @@ describe 'Git LFS API and storage' do
context 'when pushing one new and one existing lfs object' do
let(:body) do
- { 'operation' => 'upload',
+ {
+ 'operation' => 'upload',
'objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
- 'size' => 1575078
- },
+ 'size' => 1575078 },
{ 'oid' => sample_oid,
- 'size' => sample_size
- }
+ 'size' => sample_size }
]
}
end
@@ -788,11 +808,12 @@ describe 'Git LFS API and storage' do
let(:project) { create(:empty_project) }
let(:authorization) { authorize_user }
let(:body) do
- { 'operation' => 'other',
+ {
+ 'operation' => 'other',
'objects' => [
{ 'oid' => sample_oid,
- 'size' => sample_size
- }]
+ 'size' => sample_size }
+ ]
}
end
diff --git a/spec/rubocop/cop/gem_fetcher_spec.rb b/spec/rubocop/cop/gem_fetcher_spec.rb
new file mode 100644
index 00000000000..c07f6a831dc
--- /dev/null
+++ b/spec/rubocop/cop/gem_fetcher_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+
+require 'rubocop'
+require 'rubocop/rspec/support'
+
+require_relative '../../../rubocop/cop/gem_fetcher'
+
+describe RuboCop::Cop::GemFetcher do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ context 'in Gemfile' do
+ before do
+ allow(cop).to receive(:gemfile?).and_return(true)
+ end
+
+ it 'registers an offense when a gem uses `git`' do
+ inspect_source(cop, 'gem "foo", git: "https://gitlab.com/foo/bar.git"')
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([1])
+ expect(cop.highlights).to eq(['git: "https://gitlab.com/foo/bar.git"'])
+ end
+ end
+
+ it 'registers an offense when a gem uses `github`' do
+ inspect_source(cop, 'gem "foo", github: "foo/bar.git"')
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([1])
+ expect(cop.highlights).to eq(['github: "foo/bar.git"'])
+ end
+ end
+ end
+
+ context 'outside of Gemfile' do
+ it 'registers no offense' do
+ inspect_source(cop, 'gem "foo", git: "https://gitlab.com/foo/bar.git"')
+
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+end
diff --git a/spec/rubocop/cop/migration/add_column_with_default_spec.rb b/spec/rubocop/cop/migration/add_column_with_default_spec.rb
new file mode 100644
index 00000000000..6b9b6b19650
--- /dev/null
+++ b/spec/rubocop/cop/migration/add_column_with_default_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+require 'rubocop'
+require 'rubocop/rspec/support'
+
+require_relative '../../../../rubocop/cop/migration/add_column_with_default'
+
+describe RuboCop::Cop::Migration::AddColumnWithDefault do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ context 'in migration' do
+ before do
+ allow(cop).to receive(:in_migration?).and_return(true)
+ end
+
+ it 'registers an offense when add_column_with_default is used inside a change method' do
+ inspect_source(cop, 'def change; add_column_with_default :table, :column, default: false; end')
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([1])
+ end
+ end
+
+ it 'registers no offense when add_column_with_default is used inside an up method' do
+ inspect_source(cop, 'def up; add_column_with_default :table, :column, default: false; end')
+
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+
+ context 'outside of migration' do
+ it 'registers no offense' do
+ inspect_source(cop, 'def change; add_column_with_default :table, :column, default: false; end')
+
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+end
diff --git a/spec/rubocop/cop/migration/add_concurrent_foreign_key_spec.rb b/spec/rubocop/cop/migration/add_concurrent_foreign_key_spec.rb
new file mode 100644
index 00000000000..7cb24dc5646
--- /dev/null
+++ b/spec/rubocop/cop/migration/add_concurrent_foreign_key_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+require_relative '../../../../rubocop/cop/migration/add_concurrent_foreign_key'
+
+describe RuboCop::Cop::Migration::AddConcurrentForeignKey do
+ include CopHelper
+
+ let(:cop) { described_class.new }
+
+ context 'outside of a migration' do
+ it 'does not register any offenses' do
+ inspect_source(cop, 'def up; add_foreign_key(:projects, :users, column: :user_id); end')
+
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'in a migration' do
+ before do
+ allow(cop).to receive(:in_migration?).and_return(true)
+ end
+
+ it 'registers an offense when using add_foreign_key' do
+ inspect_source(cop, 'def up; add_foreign_key(:projects, :users, column: :user_id); end')
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([1])
+ end
+ end
+ end
+end
diff --git a/spec/serializers/environment_serializer_spec.rb b/spec/serializers/environment_serializer_spec.rb
index 3c37660885d..6a6df377b35 100644
--- a/spec/serializers/environment_serializer_spec.rb
+++ b/spec/serializers/environment_serializer_spec.rb
@@ -52,4 +52,147 @@ describe EnvironmentSerializer do
expect(json).to be_an_instance_of Array
end
end
+
+ context 'when representing environments within folders' do
+ let(:serializer) do
+ described_class.new(project: project).within_folders
+ end
+
+ let(:resource) { Environment.all }
+
+ subject { serializer.represent(resource) }
+
+ context 'when there is a single environment' do
+ before { create(:environment, name: 'staging') }
+
+ it 'represents one standalone environment' do
+ expect(subject.count).to eq 1
+ expect(subject.first[:name]).to eq 'staging'
+ expect(subject.first[:size]).to eq 1
+ expect(subject.first[:latest][:name]).to eq 'staging'
+ end
+ end
+
+ context 'when there are multiple environments in folder' do
+ before do
+ create(:environment, name: 'staging/my-review-1')
+ create(:environment, name: 'staging/my-review-2')
+ end
+
+ it 'represents one item that is a folder' do
+ expect(subject.count).to eq 1
+ expect(subject.first[:name]).to eq 'staging'
+ expect(subject.first[:size]).to eq 2
+ expect(subject.first[:latest][:name]).to eq 'staging/my-review-2'
+ expect(subject.first[:latest][:environment_type]).to eq 'staging'
+ end
+ end
+
+ context 'when there are multiple folders and standalone environments' do
+ before do
+ create(:environment, name: 'staging/my-review-1')
+ create(:environment, name: 'staging/my-review-2')
+ create(:environment, name: 'production/my-review-3')
+ create(:environment, name: 'testing')
+ end
+
+ it 'represents multiple items grouped within folders' do
+ expect(subject.count).to eq 3
+
+ expect(subject.first[:name]).to eq 'production'
+ expect(subject.first[:size]).to eq 1
+ expect(subject.first[:latest][:name]).to eq 'production/my-review-3'
+ expect(subject.first[:latest][:environment_type]).to eq 'production'
+ expect(subject.second[:name]).to eq 'staging'
+ expect(subject.second[:size]).to eq 2
+ expect(subject.second[:latest][:name]).to eq 'staging/my-review-2'
+ expect(subject.second[:latest][:environment_type]).to eq 'staging'
+ expect(subject.third[:name]).to eq 'testing'
+ expect(subject.third[:size]).to eq 1
+ expect(subject.third[:latest][:name]).to eq 'testing'
+ expect(subject.third[:latest][:environment_type]).to be_nil
+ end
+ end
+ end
+
+ context 'when used with pagination' do
+ let(:request) { spy('request') }
+ let(:response) { spy('response') }
+ let(:resource) { Environment.all }
+ let(:pagination) { { page: 1, per_page: 2 } }
+
+ let(:serializer) do
+ described_class.new(project: project)
+ .with_pagination(request, response)
+ end
+
+ before do
+ allow(request).to receive(:query_parameters)
+ .and_return(pagination)
+ end
+
+ subject { serializer.represent(resource) }
+
+ it 'creates a paginated serializer' do
+ expect(serializer).to be_paginated
+ end
+
+ context 'when resource is paginatable relation' do
+ context 'when there is a single environment object in relation' do
+ before { create(:environment) }
+
+ it 'serializes environments' do
+ expect(subject.first).to have_key :id
+ end
+ end
+
+ context 'when multiple environment objects are serialized' do
+ before { create_list(:environment, 3) }
+
+ it 'serializes appropriate number of objects' do
+ expect(subject.count).to be 2
+ end
+
+ it 'appends relevant headers' do
+ expect(response).to receive(:[]=).with('X-Total', '3')
+ expect(response).to receive(:[]=).with('X-Total-Pages', '2')
+ expect(response).to receive(:[]=).with('X-Per-Page', '2')
+
+ subject
+ end
+ end
+
+ context 'when grouping environments within folders' do
+ let(:serializer) do
+ described_class.new(project: project)
+ .with_pagination(request, response)
+ .within_folders
+ end
+
+ before do
+ create(:environment, name: 'staging/review-1')
+ create(:environment, name: 'staging/review-2')
+ create(:environment, name: 'production/deploy-3')
+ create(:environment, name: 'testing')
+ end
+
+ it 'paginates grouped items including ordering' do
+ expect(subject.count).to eq 2
+ expect(subject.first[:name]).to eq 'production'
+ expect(subject.second[:name]).to eq 'staging'
+ end
+
+ it 'appends correct total page count header' do
+ expect(subject).not_to be_empty
+ expect(response).to have_received(:[]=).with('X-Total', '3')
+ end
+
+ it 'appends correct page count headers' do
+ expect(subject).not_to be_empty
+ expect(response).to have_received(:[]=).with('X-Total-Pages', '2')
+ expect(response).to have_received(:[]=).with('X-Per-Page', '2')
+ end
+ end
+ end
+ end
end
diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb
index 7cbf131e41e..2aaef03cb93 100644
--- a/spec/serializers/pipeline_serializer_spec.rb
+++ b/spec/serializers/pipeline_serializer_spec.rb
@@ -52,14 +52,14 @@ describe PipelineSerializer do
expect(serializer).to be_paginated
end
- context 'when resource does is not paginatable' do
+ context 'when resource is not paginatable' do
context 'when a single pipeline object is being serialized' do
let(:resource) { create(:ci_empty_pipeline) }
let(:pagination) { { page: 1, per_page: 1 } }
it 'raises error' do
- expect { subject }
- .to raise_error(PipelineSerializer::InvalidResourceError)
+ expect { subject }.to raise_error(
+ Gitlab::Serializer::Pagination::InvalidResourceError)
end
end
end
diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb
index bb26513103d..b91234ddb1e 100644
--- a/spec/services/auth/container_registry_authentication_service_spec.rb
+++ b/spec/services/auth/container_registry_authentication_service_spec.rb
@@ -72,7 +72,7 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do
shared_examples 'a pullable and pushable' do
it_behaves_like 'a accessible' do
- let(:actions) { ['pull', 'push'] }
+ let(:actions) { %w(pull push) }
end
end
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index ceaca96e25b..8459a3d8cfb 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -79,66 +79,53 @@ describe Ci::CreatePipelineService, services: true do
context 'when commit contains a [ci skip] directive' do
let(:message) { "some message[ci skip]" }
- let(:messageFlip) { "some message[skip ci]" }
- let(:capMessage) { "some message[CI SKIP]" }
- let(:capMessageFlip) { "some message[SKIP CI]" }
+
+ ci_messages = [
+ "some message[ci skip]",
+ "some message[skip ci]",
+ "some message[CI SKIP]",
+ "some message[SKIP CI]",
+ "some message[ci_skip]",
+ "some message[skip_ci]",
+ "some message[ci-skip]",
+ "some message[skip-ci]"
+ ]
before do
allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { message }
end
- it "skips builds creation if there is [ci skip] tag in commit message" do
- commits = [{ message: message }]
- pipeline = execute(ref: 'refs/heads/master',
- before: '00000000',
- after: project.commit.id,
- commits: commits)
+ ci_messages.each do |ci_message|
+ it "skips builds creation if the commit message is #{ci_message}" do
+ commits = [{ message: ci_message }]
+ pipeline = execute(ref: 'refs/heads/master',
+ before: '00000000',
+ after: project.commit.id,
+ commits: commits)
- expect(pipeline).to be_persisted
- expect(pipeline.builds.any?).to be false
- expect(pipeline.status).to eq("skipped")
- end
-
- it "skips builds creation if there is [skip ci] tag in commit message" do
- commits = [{ message: messageFlip }]
- pipeline = execute(ref: 'refs/heads/master',
- before: '00000000',
- after: project.commit.id,
- commits: commits)
-
- expect(pipeline).to be_persisted
- expect(pipeline.builds.any?).to be false
- expect(pipeline.status).to eq("skipped")
+ expect(pipeline).to be_persisted
+ expect(pipeline.builds.any?).to be false
+ expect(pipeline.status).to eq("skipped")
+ end
end
- it "skips builds creation if there is [CI SKIP] tag in commit message" do
- commits = [{ message: capMessage }]
- pipeline = execute(ref: 'refs/heads/master',
- before: '00000000',
- after: project.commit.id,
- commits: commits)
-
- expect(pipeline).to be_persisted
- expect(pipeline.builds.any?).to be false
- expect(pipeline.status).to eq("skipped")
- end
+ it "does not skips builds creation if there is no [ci skip] or [skip ci] tag in commit message" do
+ allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { "some message" }
- it "skips builds creation if there is [SKIP CI] tag in commit message" do
- commits = [{ message: capMessageFlip }]
+ commits = [{ message: "some message" }]
pipeline = execute(ref: 'refs/heads/master',
before: '00000000',
after: project.commit.id,
commits: commits)
expect(pipeline).to be_persisted
- expect(pipeline.builds.any?).to be false
- expect(pipeline.status).to eq("skipped")
+ expect(pipeline.builds.first.name).to eq("rspec")
end
- it "does not skips builds creation if there is no [ci skip] or [skip ci] tag in commit message" do
- allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { "some message" }
+ it "does not skip builds creation if the commit message is nil" do
+ allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { nil }
- commits = [{ message: "some message" }]
+ commits = [{ message: nil }]
pipeline = execute(ref: 'refs/heads/master',
before: '00000000',
after: project.commit.id,
diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb
index ebb11166964..b818dfdd50c 100644
--- a/spec/services/ci/process_pipeline_service_spec.rb
+++ b/spec/services/ci/process_pipeline_service_spec.rb
@@ -1,8 +1,16 @@
require 'spec_helper'
-describe Ci::ProcessPipelineService, services: true do
- let(:pipeline) { create(:ci_empty_pipeline, ref: 'master') }
+describe Ci::ProcessPipelineService, :services do
let(:user) { create(:user) }
+ let(:project) { create(:empty_project) }
+
+ let(:pipeline) do
+ create(:ci_empty_pipeline, ref: 'master', project: project)
+ end
+
+ before do
+ project.add_developer(user)
+ end
describe '#execute' do
context 'start queuing next builds' do
@@ -285,7 +293,7 @@ describe Ci::ProcessPipelineService, services: true do
expect(builds.pluck(:name))
.to contain_exactly('build:1', 'build:2', 'test:1', 'test:2')
- Ci::Build.retry(pipeline.builds.find_by(name: 'test:2')).success
+ Ci::Build.retry(pipeline.builds.find_by(name: 'test:2'), user).success
expect(builds.pluck(:name)).to contain_exactly(
'build:1', 'build:2', 'test:1', 'test:2', 'test:2', 'deploy:1', 'deploy:2')
@@ -369,9 +377,7 @@ describe Ci::ProcessPipelineService, services: true do
builds.pending.update_all(status: 'success')
end
- def manual_actions
- pipeline.manual_actions
- end
+ delegate :manual_actions, to: :pipeline
def create_build(name, stage_idx, when_value = nil)
create(:ci_build,
diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb
new file mode 100644
index 00000000000..d03f7505eac
--- /dev/null
+++ b/spec/services/ci/retry_build_service_spec.rb
@@ -0,0 +1,117 @@
+require 'spec_helper'
+
+describe Ci::RetryBuildService, :services do
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project) }
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:build) { create(:ci_build, pipeline: pipeline) }
+
+ let(:service) do
+ described_class.new(project, user)
+ end
+
+ shared_examples 'build duplication' do
+ let(:build) do
+ create(:ci_build, :failed, :artifacts_expired, :erased, :trace,
+ :queued, :coverage, pipeline: pipeline)
+ end
+
+ describe 'clone attributes' do
+ described_class::CLONE_ATTRIBUTES.each do |attribute|
+ it "clones #{attribute} build attribute" do
+ expect(new_build.send(attribute)).to eq build.send(attribute)
+ end
+ end
+ end
+
+ describe 'reject attributes' do
+ described_class::REJECT_ATTRIBUTES.each do |attribute|
+ it "does not clone #{attribute} build attribute" do
+ expect(new_build.send(attribute)).not_to eq build.send(attribute)
+ end
+ end
+ end
+
+ it 'has correct number of known attributes' do
+ attributes =
+ described_class::CLONE_ATTRIBUTES +
+ described_class::IGNORE_ATTRIBUTES +
+ described_class::REJECT_ATTRIBUTES
+
+ expect(build.attributes.size).to eq(attributes.size)
+ end
+ end
+
+ describe '#execute' do
+ let(:new_build) { service.execute(build) }
+
+ context 'when user has ability to execute build' do
+ before do
+ project.add_developer(user)
+ end
+
+ it_behaves_like 'build duplication'
+
+ it 'creates a new build that represents the old one' do
+ expect(new_build.name).to eq build.name
+ end
+
+ it 'enqueues the new build' do
+ expect(new_build).to be_pending
+ end
+
+ it 'resolves todos for old build that failed' do
+ expect(MergeRequests::AddTodoWhenBuildFailsService)
+ .to receive_message_chain(:new, :close)
+
+ service.execute(build)
+ end
+
+ context 'when there are subsequent builds that are skipped' do
+ let!(:subsequent_build) do
+ create(:ci_build, :skipped, stage_idx: 1, pipeline: pipeline)
+ end
+
+ it 'resumes pipeline processing in subsequent stages' do
+ service.execute(build)
+
+ expect(subsequent_build.reload).to be_created
+ end
+ end
+ end
+
+ context 'when user does not have ability to execute build' do
+ it 'raises an error' do
+ expect { service.execute(build) }
+ .to raise_error Gitlab::Access::AccessDeniedError
+ end
+ end
+ end
+
+ describe '#reprocess' do
+ let(:new_build) { service.reprocess(build) }
+
+ context 'when user has ability to execute build' do
+ before do
+ project.add_developer(user)
+ end
+
+ it_behaves_like 'build duplication'
+
+ it 'creates a new build that represents the old one' do
+ expect(new_build.name).to eq build.name
+ end
+
+ it 'does not enqueue the new build' do
+ expect(new_build).to be_created
+ end
+ end
+
+ context 'when user does not have ability to execute build' do
+ it 'raises an error' do
+ expect { service.reprocess(build) }
+ .to raise_error Gitlab::Access::AccessDeniedError
+ end
+ end
+ end
+end
diff --git a/spec/services/ci/retry_pipeline_service_spec.rb b/spec/services/ci/retry_pipeline_service_spec.rb
new file mode 100644
index 00000000000..8b1ed6470e4
--- /dev/null
+++ b/spec/services/ci/retry_pipeline_service_spec.rb
@@ -0,0 +1,196 @@
+require 'spec_helper'
+
+describe Ci::RetryPipelineService, '#execute', :services do
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project) }
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:service) { described_class.new(project, user) }
+
+ context 'when user has ability to modify pipeline' do
+ let(:user) { create(:admin) }
+
+ context 'when there are failed builds in the last stage' do
+ before do
+ create_build('rspec 1', :success, 0)
+ create_build('rspec 2', :failed, 1)
+ create_build('rspec 3', :canceled, 1)
+ end
+
+ it 'enqueues all builds in the last stage' do
+ service.execute(pipeline)
+
+ expect(build('rspec 2')).to be_pending
+ expect(build('rspec 3')).to be_pending
+ expect(pipeline.reload).to be_running
+ end
+ end
+
+ context 'when there are failed or canceled builds in the first stage' do
+ before do
+ create_build('rspec 1', :failed, 0)
+ create_build('rspec 2', :canceled, 0)
+ create_build('rspec 3', :canceled, 1)
+ create_build('spinach 1', :canceled, 2)
+ end
+
+ it 'retries builds failed builds and marks subsequent for processing' do
+ service.execute(pipeline)
+
+ expect(build('rspec 1')).to be_pending
+ expect(build('rspec 2')).to be_pending
+ expect(build('rspec 3')).to be_created
+ expect(build('spinach 1')).to be_created
+ expect(pipeline.reload).to be_running
+ end
+ end
+
+ context 'when there is failed build present which was run on failure' do
+ before do
+ create_build('rspec 1', :failed, 0)
+ create_build('rspec 2', :canceled, 0)
+ create_build('rspec 3', :canceled, 1)
+ create_build('report 1', :failed, 2)
+ end
+
+ it 'retries builds only in the first stage' do
+ service.execute(pipeline)
+
+ expect(build('rspec 1')).to be_pending
+ expect(build('rspec 2')).to be_pending
+ expect(build('rspec 3')).to be_created
+ expect(build('report 1')).to be_created
+ expect(pipeline.reload).to be_running
+ end
+
+ it 'creates a new job for report job in this case' do
+ service.execute(pipeline)
+
+ expect(statuses.where(name: 'report 1').first).to be_retried
+ end
+ end
+
+ context 'when the last stage was skipepd' do
+ before do
+ create_build('build 1', :success, 0)
+ create_build('test 2', :failed, 1)
+ create_build('report 3', :skipped, 2)
+ create_build('report 4', :skipped, 2)
+ end
+
+ it 'retries builds only in the first stage' do
+ service.execute(pipeline)
+
+ expect(build('build 1')).to be_success
+ expect(build('test 2')).to be_pending
+ expect(build('report 3')).to be_created
+ expect(build('report 4')).to be_created
+ expect(pipeline.reload).to be_running
+ end
+ end
+
+ context 'when pipeline contains manual actions' do
+ context 'when there is a canceled manual action in first stage' do
+ before do
+ create_build('rspec 1', :failed, 0)
+ create_build('staging', :canceled, 0, :manual)
+ create_build('rspec 2', :canceled, 1)
+ end
+
+ it 'retries builds failed builds and marks subsequent for processing' do
+ service.execute(pipeline)
+
+ expect(build('rspec 1')).to be_pending
+ expect(build('staging')).to be_skipped
+ expect(build('rspec 2')).to be_created
+ expect(pipeline.reload).to be_running
+ end
+ end
+
+ context 'when there is a skipped manual action in last stage' do
+ before do
+ create_build('rspec 1', :canceled, 0)
+ create_build('rspec 2', :skipped, 0, :manual)
+ create_build('staging', :skipped, 1, :manual)
+ end
+
+ it 'retries canceled job and reprocesses manual actions' do
+ service.execute(pipeline)
+
+ expect(build('rspec 1')).to be_pending
+ expect(build('rspec 2')).to be_skipped
+ expect(build('staging')).to be_created
+ expect(pipeline.reload).to be_running
+ end
+ end
+
+ context 'when there is a created manual action in the last stage' do
+ before do
+ create_build('rspec 1', :canceled, 0)
+ create_build('staging', :created, 1, :manual)
+ end
+
+ it 'retries canceled job and does not update the manual action' do
+ service.execute(pipeline)
+
+ expect(build('rspec 1')).to be_pending
+ expect(build('staging')).to be_created
+ expect(pipeline.reload).to be_running
+ end
+ end
+
+ context 'when there is a created manual action in the first stage' do
+ before do
+ create_build('rspec 1', :canceled, 0)
+ create_build('staging', :created, 0, :manual)
+ end
+
+ it 'retries canceled job and skipps the manual action' do
+ service.execute(pipeline)
+
+ expect(build('rspec 1')).to be_pending
+ expect(build('staging')).to be_skipped
+ expect(pipeline.reload).to be_running
+ end
+ end
+ end
+
+ it 'closes all todos about failed jobs for pipeline' do
+ expect(MergeRequests::AddTodoWhenBuildFailsService)
+ .to receive_message_chain(:new, :close_all)
+
+ service.execute(pipeline)
+ end
+
+ it 'reprocesses the pipeline' do
+ expect(pipeline).to receive(:process!)
+
+ service.execute(pipeline)
+ end
+ end
+
+ context 'when user is not allowed to retry pipeline' do
+ it 'raises an error' do
+ expect { service.execute(pipeline) }
+ .to raise_error Gitlab::Access::AccessDeniedError
+ end
+ end
+
+ def statuses
+ pipeline.reload.statuses
+ end
+
+ def build(name)
+ statuses.latest.find_by(name: name)
+ end
+
+ def create_build(name, status, stage_num, on = 'on_success')
+ create(:ci_build, name: name,
+ status: status,
+ stage: "stage_#{stage_num}",
+ stage_idx: stage_num,
+ when: on,
+ pipeline: pipeline) do |build|
+ pipeline.update_status
+ end
+ end
+end
diff --git a/spec/services/ci/update_runner_service_spec.rb b/spec/services/ci/update_runner_service_spec.rb
new file mode 100644
index 00000000000..e429fcfc72f
--- /dev/null
+++ b/spec/services/ci/update_runner_service_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+describe Ci::UpdateRunnerService, :services do
+ let(:runner) { create(:ci_runner) }
+
+ describe '#update' do
+ before do
+ allow(runner).to receive(:tick_runner_queue)
+ end
+
+ context 'with description params' do
+ let(:params) { { description: 'new runner' } }
+
+ it 'updates the runner and ticking the queue' do
+ expect(update).to be_truthy
+
+ runner.reload
+
+ expect(runner).to have_received(:tick_runner_queue)
+ expect(runner.description).to eq('new runner')
+ end
+ end
+
+ context 'when params are not valid' do
+ let(:params) { { run_untagged: false } }
+
+ it 'does not update and give false because it is not valid' do
+ expect(update).to be_falsey
+
+ runner.reload
+
+ expect(runner).not_to have_received(:tick_runner_queue)
+ expect(runner.run_untagged).to be_truthy
+ end
+ end
+
+ def update
+ described_class.new(runner).update(params)
+ end
+ end
+end
diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb
index cf0a18aacec..18b964e2453 100644
--- a/spec/services/create_deployment_service_spec.rb
+++ b/spec/services/create_deployment_service_spec.rb
@@ -9,7 +9,8 @@ describe CreateDeploymentService, services: true do
describe '#execute' do
let(:options) { nil }
let(:params) do
- { environment: 'production',
+ {
+ environment: 'production',
ref: 'master',
tag: false,
sha: '97de212e80737a608d939f648d959671fb0a0142',
@@ -83,10 +84,11 @@ describe CreateDeploymentService, services: true do
context 'for environment with invalid name' do
let(:params) do
- { environment: 'name,with,commas',
+ {
+ environment: 'name,with,commas',
ref: 'master',
tag: false,
- sha: '97de212e80737a608d939f648d959671fb0a0142',
+ sha: '97de212e80737a608d939f648d959671fb0a0142'
}
end
@@ -101,7 +103,8 @@ describe CreateDeploymentService, services: true do
context 'when variables are used' do
let(:params) do
- { environment: 'review-apps/$CI_BUILD_REF_NAME',
+ {
+ environment: 'review-apps/$CI_BUILD_REF_NAME',
ref: 'master',
tag: false,
sha: '97de212e80737a608d939f648d959671fb0a0142',
@@ -234,7 +237,11 @@ describe CreateDeploymentService, services: true do
context 'when build is retried' do
it_behaves_like 'does create environment and deployment' do
- let(:deployable) { Ci::Build.retry(build) }
+ before do
+ project.add_developer(user)
+ end
+
+ let(:deployable) { Ci::Build.retry(build, user) }
subject { deployable.success }
end
diff --git a/spec/services/delete_user_service_spec.rb b/spec/services/delete_user_service_spec.rb
deleted file mode 100644
index 418a12a83a9..00000000000
--- a/spec/services/delete_user_service_spec.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-require 'spec_helper'
-
-describe DeleteUserService, services: true do
- describe "Deletes a user and all their personal projects" do
- let!(:user) { create(:user) }
- let!(:current_user) { create(:user) }
- let!(:namespace) { create(:namespace, owner: user) }
- let!(:project) { create(:project, namespace: namespace) }
-
- context 'no options are given' do
- it 'deletes the user' do
- user_data = DeleteUserService.new(current_user).execute(user)
-
- expect { user_data['email'].to eq(user.email) }
- expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound)
- expect { Namespace.with_deleted.find(user.namespace.id) }.to raise_error(ActiveRecord::RecordNotFound)
- end
-
- it 'will delete the project in the near future' do
- expect_any_instance_of(Projects::DestroyService).to receive(:async_execute).once
-
- DeleteUserService.new(current_user).execute(user)
- end
- end
-
- context "solo owned groups present" do
- let(:solo_owned) { create(:group) }
- let(:member) { create(:group_member) }
- let(:user) { member.user }
-
- before do
- solo_owned.group_members = [member]
- DeleteUserService.new(current_user).execute(user)
- end
-
- it 'does not delete the user' do
- expect(User.find(user.id)).to eq user
- end
- end
-
- context "deletions with solo owned groups" do
- let(:solo_owned) { create(:group) }
- let(:member) { create(:group_member) }
- let(:user) { member.user }
-
- before do
- solo_owned.group_members = [member]
- DeleteUserService.new(current_user).execute(user, delete_solo_owned_groups: true)
- end
-
- it 'deletes solo owned groups' do
- expect { Project.find(solo_owned.id) }.to raise_error(ActiveRecord::RecordNotFound)
- end
-
- it 'deletes the user' do
- expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound)
- end
- end
- end
-end
diff --git a/spec/services/destroy_group_service_spec.rb b/spec/services/groups/destroy_service_spec.rb
index 538e85cdc89..32c2ed8cae7 100644
--- a/spec/services/destroy_group_service_spec.rb
+++ b/spec/services/groups/destroy_service_spec.rb
@@ -1,13 +1,17 @@
require 'spec_helper'
-describe DestroyGroupService, services: true do
+describe Groups::DestroyService, services: true do
include DatabaseConnectionHelpers
- let!(:user) { create(:user) }
- let!(:group) { create(:group) }
- let!(:project) { create(:project, namespace: group) }
+ let!(:user) { create(:user) }
+ let!(:group) { create(:group) }
+ let!(:project) { create(:project, namespace: group) }
let!(:gitlab_shell) { Gitlab::Shell.new }
- let!(:remove_path) { group.path + "+#{group.id}+deleted" }
+ let!(:remove_path) { group.path + "+#{group.id}+deleted" }
+
+ before do
+ group.add_user(user, Gitlab::Access::OWNER)
+ end
shared_examples 'group destruction' do |async|
context 'database records' do
@@ -15,8 +19,8 @@ describe DestroyGroupService, services: true do
destroy_group(group, user, async)
end
- it { expect(Group.all).not_to include(group) }
- it { expect(Project.all).not_to include(project) }
+ it { expect(Group.unscoped.all).not_to include(group) }
+ it { expect(Project.unscoped.all).not_to include(project) }
end
context 'file system' do
@@ -32,7 +36,7 @@ describe DestroyGroupService, services: true do
context 'Sidekiq fake' do
before do
- # Dont run sidekiq to check if renamed repository exists
+ # Don't run sidekiq to check if renamed repository exists
Sidekiq::Testing.fake! { destroy_group(group, user, async) }
end
@@ -43,9 +47,9 @@ describe DestroyGroupService, services: true do
def destroy_group(group, user, async)
if async
- DestroyGroupService.new(group, user).async_execute
+ Groups::DestroyService.new(group, user).async_execute
else
- DestroyGroupService.new(group, user).execute
+ Groups::DestroyService.new(group, user).execute
end
end
end
@@ -80,7 +84,7 @@ describe DestroyGroupService, services: true do
# Kick off the initial group destroy in a new thread, so that
# it doesn't share this spec's database transaction.
- Thread.new { DestroyGroupService.new(group, user).async_execute }.join(5)
+ Thread.new { Groups::DestroyService.new(group, user).async_execute }.join(5)
group_record = run_with_new_database_connection do |conn|
conn.execute("SELECT * FROM namespaces WHERE id = #{group.id}").first
@@ -95,4 +99,13 @@ describe DestroyGroupService, services: true do
describe 'synchronous delete' do
it_behaves_like 'group destruction', false
end
+
+ context 'projects in pending_delete' do
+ before do
+ project.pending_delete = true
+ project.save
+ end
+
+ it_behaves_like 'group destruction', false
+ end
end
diff --git a/spec/services/issues/build_service_spec.rb b/spec/services/issues/build_service_spec.rb
index 4cfba35c830..09807e5d35b 100644
--- a/spec/services/issues/build_service_spec.rb
+++ b/spec/services/issues/build_service_spec.rb
@@ -120,11 +120,20 @@ describe Issues::BuildService, services: true do
end
describe '#execute' do
+ let(:milestone) { create(:milestone, project: project) }
+
it 'builds a new issues with given params' do
- issue = described_class.new(project, user, title: 'Issue #1', description: 'Issue description').execute
+ issue = described_class.new(
+ project,
+ user,
+ title: 'Issue #1',
+ description: 'Issue description',
+ milestone_id: milestone.id,
+ ).execute
expect(issue.title).to eq('Issue #1')
expect(issue.description).to eq('Issue description')
+ expect(issue.milestone).to eq(milestone)
end
end
end
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index 30578ee4c7d..6045d00ff09 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -46,6 +46,7 @@ describe Issues::CreateService, services: true do
expect(issue).to be_persisted
expect(issue.title).to eq('Awesome issue')
+ expect(issue.description).to eq('please fix')
expect(issue.assignee).to be_nil
expect(issue.labels).to be_empty
expect(issue.milestone).to be_nil
@@ -229,16 +230,6 @@ describe Issues::CreateService, services: true do
expect { issue }.not_to change{SpamLog.last.recaptcha_verified}
end
end
-
- context 'when spam log title does not match the issue title' do
- before do
- opts[:title] = 'Another issue'
- end
-
- it 'does not mark spam_log as recaptcha_verified' do
- expect { issue }.not_to change{SpamLog.last.recaptcha_verified}
- end
- end
end
context 'when recaptcha was not verified' do
diff --git a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb
index bb7830c7eea..d80fb8a1af1 100644
--- a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb
+++ b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb
@@ -17,7 +17,7 @@ describe MergeRequests::AddTodoWhenBuildFailsService do
described_class.new(project, user, commit_message: 'Awesome message')
end
- let(:todo_service) { TodoService.new }
+ let(:todo_service) { spy('todo service') }
let(:merge_request) do
create(:merge_request, merge_user: user,
@@ -107,4 +107,27 @@ describe MergeRequests::AddTodoWhenBuildFailsService do
end
end
end
+
+ describe '#close_all' do
+ context 'when using pipeline that belongs to merge request' do
+ it 'resolves todos about failed builds for pipeline' do
+ service.close_all(pipeline)
+
+ expect(todo_service)
+ .to have_received(:merge_request_build_retried)
+ .with(merge_request)
+ end
+ end
+
+ context 'when pipeline is not related to merge request' do
+ let(:pipeline) { create(:ci_empty_pipeline) }
+
+ it 'does not resolve any todos about failed builds' do
+ service.close_all(pipeline)
+
+ expect(todo_service)
+ .not_to have_received(:merge_request_build_retried)
+ end
+ end
+ end
end
diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb
index dc945ca4868..0768f644036 100644
--- a/spec/services/merge_requests/build_service_spec.rb
+++ b/spec/services/merge_requests/build_service_spec.rb
@@ -44,15 +44,14 @@ describe MergeRequests::BuildService, services: true do
end
end
- context 'missing target branch' do
- let(:target_branch) { '' }
+ context 'when target branch is missing' do
+ let(:target_branch) { nil }
+ let(:commits) { Commit.decorate([commit_1], project) }
- it 'forbids the merge request from being created' do
+ it 'creates compare object with target branch as default branch' do
expect(merge_request.can_be_created).to eq(false)
- end
-
- it 'adds an error message to the merge request' do
- expect(merge_request.errors).to contain_exactly('You must select source and target branch')
+ expect(merge_request.compare).to be_present
+ expect(merge_request.target_branch).to eq(project.default_branch)
end
end
diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb
index 5a89acc96a4..d96f819e66a 100644
--- a/spec/services/merge_requests/merge_service_spec.rb
+++ b/spec/services/merge_requests/merge_service_spec.rb
@@ -149,35 +149,46 @@ describe MergeRequests::MergeService, services: true do
context "error handling" do
let(:service) { MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message') }
- it 'saves error if there is an exception' do
- allow(service).to receive(:repository).and_raise("error message")
+ before do
+ allow(Rails.logger).to receive(:error)
+ end
+ it 'logs and saves error if there is an exception' do
+ error_message = 'error message'
+
+ allow(service).to receive(:repository).and_raise("error message")
allow(service).to receive(:execute_hooks)
service.execute(merge_request)
- expect(merge_request.merge_error).to eq("Something went wrong during merge: error message")
+ expect(merge_request.merge_error).to include(error_message)
+ expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message))
end
- it 'saves error if there is an PreReceiveError exception' do
- allow(service).to receive(:repository).and_raise(GitHooksService::PreReceiveError, "error")
+ it 'logs and saves error if there is an PreReceiveError exception' do
+ error_message = 'error message'
+ allow(service).to receive(:repository).and_raise(GitHooksService::PreReceiveError, error_message)
allow(service).to receive(:execute_hooks)
service.execute(merge_request)
- expect(merge_request.merge_error).to eq("error")
+ expect(merge_request.merge_error).to include(error_message)
+ expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message))
end
- it 'aborts if there is a merge conflict' do
+ it 'logs and saves error if there is a merge conflict' do
+ error_message = 'Conflicts detected during merge'
+
allow_any_instance_of(Repository).to receive(:merge).and_return(false)
allow(service).to receive(:execute_hooks)
service.execute(merge_request)
- expect(merge_request.open?).to be_truthy
+ expect(merge_request).to be_open
expect(merge_request.merge_commit_sha).to be_nil
- expect(merge_request.merge_error).to eq("Conflicts detected during merge")
+ expect(merge_request.merge_error).to include(error_message)
+ expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message))
end
end
end
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 2cc21acab7b..983dac6efdb 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -287,41 +287,64 @@ describe MergeRequests::RefreshService, services: true do
it 'references the commit that caused the Work in Progress status' do
refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
-
allow(refresh_service).to receive(:find_new_commits)
refresh_service.instance_variable_set("@commits", [
- instance_double(
- Commit,
+ double(
id: 'aaaaaaa',
+ sha: '38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e',
short_id: 'aaaaaaa',
title: 'Fix issue',
work_in_progress?: false
),
- instance_double(
- Commit,
+ double(
id: 'bbbbbbb',
+ sha: '498214de67004b1da3d820901307bed2a68a8ef6',
short_id: 'bbbbbbb',
title: 'fixup! Fix issue',
work_in_progress?: true,
to_reference: 'bbbbbbb'
),
- instance_double(
- Commit,
+ double(
id: 'ccccccc',
+ sha: '1b12f15a11fc6e62177bef08f47bc7b5ce50b141',
short_id: 'ccccccc',
title: 'fixup! Fix issue',
work_in_progress?: true,
to_reference: 'ccccccc'
),
])
-
refresh_service.execute(@oldrev, @newrev, 'refs/heads/wip')
reload_mrs
-
expect(@merge_request.notes.last.note).to eq(
"marked as a **Work In Progress** from bbbbbbb"
)
end
+
+ it 'does not mark as WIP based on commits that do not belong to an MR' do
+ allow(refresh_service).to receive(:find_new_commits)
+ refresh_service.instance_variable_set("@commits", [
+ double(
+ id: 'aaaaaaa',
+ sha: 'aaaaaaa',
+ short_id: 'aaaaaaa',
+ title: 'Fix issue',
+ work_in_progress?: false
+ ),
+ double(
+ id: 'bbbbbbb',
+ sha: 'bbbbbbbb',
+ short_id: 'bbbbbbb',
+ title: 'fixup! Fix issue',
+ work_in_progress?: true,
+ to_reference: 'bbbbbbb'
+ )
+ ])
+
+ refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
+ reload_mrs
+
+ expect(@merge_request.work_in_progress?).to be_falsey
+ end
end
def reload_mrs
diff --git a/spec/services/merge_requests/resolve_service_spec.rb b/spec/services/merge_requests/resolve_service_spec.rb
index a0e51681725..c3b468ac47f 100644
--- a/spec/services/merge_requests/resolve_service_spec.rb
+++ b/spec/services/merge_requests/resolve_service_spec.rb
@@ -59,8 +59,8 @@ describe MergeRequests::ResolveService do
it 'creates a commit with the correct parents' do
expect(merge_request.source_branch_head.parents.map(&:id)).
- to eq(['1450cd639e0bc6721eb02800169e464f212cde06',
- '824be604a34828eb682305f0d963056cfac87b2d'])
+ to eq(%w(1450cd639e0bc6721eb02800169e464f212cde06
+ 824be604a34828eb682305f0d963056cfac87b2d))
end
end
@@ -125,8 +125,8 @@ describe MergeRequests::ResolveService do
it 'creates a commit with the correct parents' do
expect(merge_request.source_branch_head.parents.map(&:id)).
- to eq(['1450cd639e0bc6721eb02800169e464f212cde06',
- '824be604a34828eb682305f0d963056cfac87b2d'])
+ to eq(%w(1450cd639e0bc6721eb02800169e464f212cde06
+ 824be604a34828eb682305f0d963056cfac87b2d))
end
it 'sets the content to the content given' do
diff --git a/spec/services/notes/delete_service_spec.rb b/spec/services/notes/destroy_service_spec.rb
index 1d0a747a480..f53f96e0c2b 100644
--- a/spec/services/notes/delete_service_spec.rb
+++ b/spec/services/notes/destroy_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Notes::DeleteService, services: true do
+describe Notes::DestroyService, services: true do
describe '#execute' do
it 'deletes a note' do
project = create(:empty_project)
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 7cf2cd9968f..839250b7d84 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -146,6 +146,16 @@ describe NotificationService, services: true do
should_not_email(@u_lazy_participant)
end
+ it "emails the note author if they've opted into notifications about their activity" do
+ add_users_with_subscription(note.project, issue)
+ note.author.notified_of_own_activity = true
+ reset_delivered_emails!
+
+ notification.new_note(note)
+
+ should_email(note.author)
+ end
+
it 'filters out "mentioned in" notes' do
mentioned_note = SystemNoteService.cross_reference(mentioned_issue, issue, issue.author)
@@ -476,6 +486,20 @@ describe NotificationService, services: true do
should_not_email(issue.assignee)
end
+ it "emails the author if they've opted into notifications about their activity" do
+ issue.author.notified_of_own_activity = true
+
+ notification.new_issue(issue, issue.author)
+
+ should_email(issue.author)
+ end
+
+ it "doesn't email the author if they haven't opted into notifications about their activity" do
+ notification.new_issue(issue, issue.author)
+
+ should_not_email(issue.author)
+ end
+
it "emails subscribers of the issue's labels" do
user_1 = create(:user)
user_2 = create(:user)
@@ -665,6 +689,19 @@ describe NotificationService, services: true do
should_email(subscriber_to_label_2)
end
+ it "emails the current user if they've opted into notifications about their activity" do
+ subscriber_to_label_2.notified_of_own_activity = true
+ notification.relabeled_issue(issue, [group_label_2, label_2], subscriber_to_label_2)
+
+ should_email(subscriber_to_label_2)
+ end
+
+ it "doesn't email the current user if they haven't opted into notifications about their activity" do
+ notification.relabeled_issue(issue, [group_label_2, label_2], subscriber_to_label_2)
+
+ should_not_email(subscriber_to_label_2)
+ end
+
it "doesn't send email to anyone but subscribers of the given labels" do
notification.relabeled_issue(issue, [group_label_2, label_2], @u_disabled)
@@ -818,6 +855,20 @@ describe NotificationService, services: true do
should_not_email(@u_lazy_participant)
end
+ it "emails the author if they've opted into notifications about their activity" do
+ merge_request.author.notified_of_own_activity = true
+
+ notification.new_merge_request(merge_request, merge_request.author)
+
+ should_email(merge_request.author)
+ end
+
+ it "doesn't email the author if they haven't opted into notifications about their activity" do
+ notification.new_merge_request(merge_request, merge_request.author)
+
+ should_not_email(merge_request.author)
+ end
+
it "emails subscribers of the merge request's labels" do
user_1 = create(:user)
user_2 = create(:user)
@@ -1013,6 +1064,14 @@ describe NotificationService, services: true do
should_not_email(@u_watcher)
end
+ it "notifies the merger when merge_when_build_succeeds is false but they've opted into notifications about their activity" do
+ merge_request.merge_when_build_succeeds = false
+ @u_watcher.notified_of_own_activity = true
+ notification.merge_mr(merge_request, @u_watcher)
+
+ should_email(@u_watcher)
+ end
+
it_behaves_like 'participating notifications' do
let(:participant) { create(:user, username: 'user-participant') }
let(:issuable) { merge_request }
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index af515ad2e0e..62f21049b0b 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -50,7 +50,7 @@ describe Projects::CreateService, '#execute', services: true do
context 'error handling' do
it 'handles invalid options' do
- opts.merge!({ default_branch: 'master' } )
+ opts[:default_branch] = 'master'
expect(create_project(user, opts)).to eq(nil)
end
end
@@ -67,7 +67,7 @@ describe Projects::CreateService, '#execute', services: true do
context 'wiki_enabled false does not create wiki repository directory' do
it do
- opts.merge!(wiki_enabled: false)
+ opts[:wiki_enabled] = false
project = create_project(user, opts)
path = ProjectWiki.new(project, user).send(:path_to_repo)
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index 3faa88c00a1..74bfba44dfd 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -50,6 +50,25 @@ describe Projects::DestroyService, services: true do
it { expect(Dir.exist?(remove_path)).to be_truthy }
end
+ context 'when flushing caches fail' do
+ before do
+ new_user = create(:user)
+ project.team.add_user(new_user, Gitlab::Access::DEVELOPER)
+ allow_any_instance_of(Projects::DestroyService).to receive(:flush_caches).and_raise(Redis::CannotConnectError)
+ end
+
+ it 'keeps project team intact upon an error' do
+ Sidekiq::Testing.inline! do
+ begin
+ destroy_project(project, user, {})
+ rescue Redis::CannotConnectError
+ end
+ end
+
+ expect(project.team.members.count).to eq 1
+ end
+ end
+
context 'with async_execute' do
let(:async) { true }
diff --git a/spec/services/protected_branches/create_service_spec.rb b/spec/services/protected_branches/create_service_spec.rb
index 7d4eff3b6ef..6ea8f309981 100644
--- a/spec/services/protected_branches/create_service_spec.rb
+++ b/spec/services/protected_branches/create_service_spec.rb
@@ -6,8 +6,8 @@ describe ProtectedBranches::CreateService, services: true do
let(:params) do
{
name: 'master',
- merge_access_levels_attributes: [ { access_level: Gitlab::Access::MASTER } ],
- push_access_levels_attributes: [ { access_level: Gitlab::Access::MASTER } ]
+ merge_access_levels_attributes: [{ access_level: Gitlab::Access::MASTER }],
+ push_access_levels_attributes: [{ access_level: Gitlab::Access::MASTER }]
}
end
diff --git a/spec/services/spam_service_spec.rb b/spec/services/spam_service_spec.rb
index 271c17dd8c0..4ce3b95aa87 100644
--- a/spec/services/spam_service_spec.rb
+++ b/spec/services/spam_service_spec.rb
@@ -1,46 +1,61 @@
require 'spec_helper'
describe SpamService, services: true do
- describe '#check' do
- let(:project) { create(:project, :public) }
- let(:issue) { create(:issue, project: project) }
- let(:request) { double(:request, env: {}) }
+ describe '#when_recaptcha_verified' do
+ def check_spam(issue, request, recaptcha_verified)
+ described_class.new(issue, request).when_recaptcha_verified(recaptcha_verified) do
+ 'yielded'
+ end
+ end
+
+ it 'yields block when recaptcha was already verified' do
+ issue = build_stubbed(:issue)
- def check_spam(issue, request)
- described_class.new(issue, request).check
+ expect(check_spam(issue, nil, true)).to eql('yielded')
end
- context 'when indicated as spam by akismet' do
- before { allow(AkismetService).to receive(:new).and_return(double(is_spam?: true)) }
+ context 'when recaptcha was not verified' do
+ let(:project) { create(:project, :public) }
+ let(:issue) { create(:issue, project: project) }
+ let(:request) { double(:request, env: {}) }
- it 'returns false when request is missing' do
- expect(check_spam(issue, nil)).to be_falsey
- end
+ context 'when indicated as spam by akismet' do
+ before { allow(AkismetService).to receive(:new).and_return(double(is_spam?: true)) }
- it 'returns false when issue is not public' do
- issue = create(:issue, project: create(:project, :private))
+ it 'doesnt check as spam when request is missing' do
+ check_spam(issue, nil, false)
- expect(check_spam(issue, request)).to be_falsey
- end
+ expect(issue.spam).to be_falsey
+ end
- it 'returns true' do
- expect(check_spam(issue, request)).to be_truthy
- end
+ it 'checks as spam' do
+ check_spam(issue, request, false)
- it 'creates a spam log' do
- expect { check_spam(issue, request) }.to change { SpamLog.count }.from(0).to(1)
- end
- end
+ expect(issue.spam).to be_truthy
+ end
- context 'when not indicated as spam by akismet' do
- before { allow(AkismetService).to receive(:new).and_return(double(is_spam?: false)) }
+ it 'creates a spam log' do
+ expect { check_spam(issue, request, false) }
+ .to change { SpamLog.count }.from(0).to(1)
+ end
- it 'returns false' do
- expect(check_spam(issue, request)).to be_falsey
+ it 'doesnt yield block' do
+ expect(check_spam(issue, request, false))
+ .to eql(SpamLog.last)
+ end
end
- it 'does not create a spam log' do
- expect { check_spam(issue, request) }.not_to change { SpamLog.count }
+ context 'when not indicated as spam by akismet' do
+ before { allow(AkismetService).to receive(:new).and_return(double(is_spam?: false)) }
+
+ it 'returns false' do
+ expect(check_spam(issue, request, false)).to be_falsey
+ end
+
+ it 'does not create a spam log' do
+ expect { check_spam(issue, request, false) }
+ .not_to change { SpamLog.count }
+ end
end
end
end
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 7f027ae02a2..eca5a418f2a 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -631,7 +631,7 @@ describe SystemNoteService, services: true do
jira_service_settings
end
- noteable_types = ["merge_requests", "commit"]
+ noteable_types = %w(merge_requests commit)
noteable_types.each do |type|
context "when noteable is a #{type}" do
diff --git a/spec/services/create_tag_service_spec.rb b/spec/services/tags/create_service_spec.rb
index 7dc43c50b0d..5478b8c9ec0 100644
--- a/spec/services/create_tag_service_spec.rb
+++ b/spec/services/tags/create_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe CreateTagService, services: true do
+describe Tags::CreateService, services: true do
let(:project) { create(:project) }
let(:repository) { project.repository }
let(:user) { create(:user) }
diff --git a/spec/services/delete_tag_service_spec.rb b/spec/services/tags/destroy_service_spec.rb
index 477551f5036..a388c93379a 100644
--- a/spec/services/delete_tag_service_spec.rb
+++ b/spec/services/tags/destroy_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe DeleteTagService, services: true do
+describe Tags::DestroyService, services: true do
let(:project) { create(:project) }
let(:repository) { project.repository }
let(:user) { create(:user) }
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
index 13d584a8975..9f24cc0f3f2 100644
--- a/spec/services/todo_service_spec.rb
+++ b/spec/services/todo_service_spec.rb
@@ -9,7 +9,9 @@ describe TodoService, services: true do
let(:admin) { create(:admin) }
let(:john_doe) { create(:user) }
let(:project) { create(:project) }
- let(:mentions) { [author, assignee, john_doe, member, guest, non_member, admin].map(&:to_reference).join(' ') }
+ let(:mentions) { 'FYI: ' + [author, assignee, john_doe, member, guest, non_member, admin].map(&:to_reference).join(' ') }
+ let(:directly_addressed) { [author, assignee, john_doe, member, guest, non_member, admin].map(&:to_reference).join(' ') }
+ let(:directly_addressed_and_mentioned) { member.to_reference + ", what do you think? cc: " + [guest, admin].map(&:to_reference).join(' ') }
let(:service) { described_class.new }
before do
@@ -21,8 +23,10 @@ describe TodoService, services: true do
describe 'Issues' do
let(:issue) { create(:issue, project: project, assignee: john_doe, author: author, description: "- [ ] Task 1\n- [ ] Task 2 #{mentions}") }
+ let(:addressed_issue) { create(:issue, project: project, assignee: john_doe, author: author, description: "#{directly_addressed}\n- [ ] Task 1\n- [ ] Task 2") }
let(:unassigned_issue) { create(:issue, project: project, assignee: nil) }
let(:confidential_issue) { create(:issue, :confidential, project: project, author: author, assignee: assignee, description: mentions) }
+ let(:addressed_confident_issue) { create(:issue, :confidential, project: project, author: author, assignee: assignee, description: directly_addressed) }
describe '#new_issue' do
it 'creates a todo if assigned' do
@@ -52,6 +56,26 @@ describe TodoService, services: true do
should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED)
end
+ it 'creates a directly addressed todo for each valid addressed user' do
+ service.new_issue(addressed_issue, author)
+
+ should_create_todo(user: member, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: guest, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: author, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: john_doe, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: non_member, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
+ end
+
+ it 'creates correct todos for each valid user based on the type of mention' do
+ issue.update(description: directly_addressed_and_mentioned)
+
+ service.new_issue(issue, author)
+
+ should_create_todo(user: member, target: issue, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: admin, target: issue, action: Todo::MENTIONED)
+ should_create_todo(user: guest, target: issue, action: Todo::MENTIONED)
+ end
+
it 'does not create todo if user can not see the issue when issue is confidential' do
service.new_issue(confidential_issue, john_doe)
@@ -63,6 +87,17 @@ describe TodoService, services: true do
should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
end
+ it 'does not create directly addressed todo if user cannot see the issue when issue is confidential' do
+ service.new_issue(addressed_confident_issue, john_doe)
+
+ should_create_todo(user: assignee, target: addressed_confident_issue, author: john_doe, action: Todo::ASSIGNED)
+ should_create_todo(user: author, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: member, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: admin, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: guest, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: john_doe, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
+ end
+
context 'when a private group is mentioned' do
let(:group) { create :group, :private }
let(:project) { create :project, :private, group: group }
@@ -94,12 +129,38 @@ describe TodoService, services: true do
should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED)
end
+ it 'creates a todo for each valid user based on the type of mention' do
+ issue.update(description: directly_addressed_and_mentioned)
+
+ service.update_issue(issue, author)
+
+ should_create_todo(user: member, target: issue, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: guest, target: issue, action: Todo::MENTIONED)
+ should_create_todo(user: admin, target: issue, action: Todo::MENTIONED)
+ end
+
+ it 'creates a directly addressed todo for each valid addressed user' do
+ service.update_issue(addressed_issue, author)
+
+ should_create_todo(user: member, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: guest, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: john_doe, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: author, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: non_member, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
+ end
+
it 'does not create a todo if user was already mentioned' do
create(:todo, :mentioned, user: member, project: project, target: issue, author: author)
expect { service.update_issue(issue, author) }.not_to change(member.todos, :count)
end
+ it 'does not create a directly addressed todo if user was already mentioned or addressed' do
+ create(:todo, :directly_addressed, user: member, project: project, target: addressed_issue, author: author)
+
+ expect { service.update_issue(addressed_issue, author) }.not_to change(member.todos, :count)
+ end
+
it 'does not create todo if user can not see the issue when issue is confidential' do
service.update_issue(confidential_issue, john_doe)
@@ -111,6 +172,17 @@ describe TodoService, services: true do
should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
end
+ it 'does not create a directly addressed todo if user can not see the issue when issue is confidential' do
+ service.update_issue(addressed_confident_issue, john_doe)
+
+ should_create_todo(user: author, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: assignee, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: member, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: admin, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: guest, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: john_doe, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
+ end
+
context 'issues with a task list' do
it 'does not create todo when tasks are marked as completed' do
issue.update(description: "- [x] Task 1\n- [X] Task 2 #{mentions}")
@@ -125,6 +197,19 @@ describe TodoService, services: true do
should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED)
end
+ it 'does not create directly addressed todo when tasks are marked as completed' do
+ addressed_issue.update(description: "#{directly_addressed}\n- [x] Task 1\n- [x] Task 2\n")
+
+ service.update_issue(addressed_issue, author)
+
+ should_not_create_todo(user: admin, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: assignee, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: author, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: john_doe, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: member, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: non_member, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
+ end
+
it 'does not raise an error when description not change' do
issue.update(title: 'Sample')
@@ -202,39 +287,51 @@ describe TodoService, services: true do
end
end
- shared_examples 'marking todos as done' do |meth|
- let!(:first_todo) { create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) }
- let!(:second_todo) { create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) }
+ shared_examples 'updating todos state' do |meth, state, new_state|
+ let!(:first_todo) { create(:todo, state, user: john_doe, project: project, target: issue, author: author) }
+ let!(:second_todo) { create(:todo, state, user: john_doe, project: project, target: issue, author: author) }
- it 'marks related todos for the user as done' do
+ it 'updates related todos for the user with the new_state' do
service.send(meth, collection, john_doe)
- expect(first_todo.reload).to be_done
- expect(second_todo.reload).to be_done
+ expect(first_todo.reload.state?(new_state)).to be true
+ expect(second_todo.reload.state?(new_state)).to be true
end
describe 'cached counts' do
it 'updates when todos change' do
- expect(john_doe.todos_done_count).to eq(0)
- expect(john_doe.todos_pending_count).to eq(2)
+ expect(john_doe.todos.where(state: new_state).count).to eq(0)
+ expect(john_doe.todos.where(state: state).count).to eq(2)
expect(john_doe).to receive(:update_todos_count_cache).and_call_original
service.send(meth, collection, john_doe)
- expect(john_doe.todos_done_count).to eq(2)
- expect(john_doe.todos_pending_count).to eq(0)
+ expect(john_doe.todos.where(state: new_state).count).to eq(2)
+ expect(john_doe.todos.where(state: state).count).to eq(0)
end
end
end
describe '#mark_todos_as_done' do
- it_behaves_like 'marking todos as done', :mark_todos_as_done do
+ it_behaves_like 'updating todos state', :mark_todos_as_done, :pending, :done do
let(:collection) { [first_todo, second_todo] }
end
end
describe '#mark_todos_as_done_by_ids' do
- it_behaves_like 'marking todos as done', :mark_todos_as_done_by_ids do
+ it_behaves_like 'updating todos state', :mark_todos_as_done_by_ids, :pending, :done do
+ let(:collection) { [first_todo, second_todo].map(&:id) }
+ end
+ end
+
+ describe '#mark_todos_as_pending' do
+ it_behaves_like 'updating todos state', :mark_todos_as_pending, :done, :pending do
+ let(:collection) { [first_todo, second_todo] }
+ end
+ end
+
+ describe '#mark_todos_as_pending_by_ids' do
+ it_behaves_like 'updating todos state', :mark_todos_as_pending_by_ids, :done, :pending do
let(:collection) { [first_todo, second_todo].map(&:id) }
end
end
@@ -244,8 +341,11 @@ describe TodoService, services: true do
let!(:second_todo) { create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) }
let(:confidential_issue) { create(:issue, :confidential, project: project, author: author, assignee: assignee) }
let(:note) { create(:note, project: project, noteable: issue, author: john_doe, note: mentions) }
+ let(:addressed_note) { create(:note, project: project, noteable: issue, author: john_doe, note: directly_addressed) }
let(:note_on_commit) { create(:note_on_commit, project: project, author: john_doe, note: mentions) }
+ let(:addressed_note_on_commit) { create(:note_on_commit, project: project, author: john_doe, note: directly_addressed) }
let(:note_on_confidential_issue) { create(:note_on_issue, noteable: confidential_issue, project: project, note: mentions) }
+ let(:addressed_note_on_confidential_issue) { create(:note_on_issue, noteable: confidential_issue, project: project, note: directly_addressed) }
let(:note_on_project_snippet) { create(:note_on_project_snippet, project: project, author: john_doe, note: mentions) }
let(:system_note) { create(:system_note, project: project, noteable: issue) }
@@ -276,6 +376,26 @@ describe TodoService, services: true do
should_not_create_todo(user: non_member, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
end
+ it 'creates a todo for each valid user based on the type of mention' do
+ note.update(note: directly_addressed_and_mentioned)
+
+ service.new_note(note, john_doe)
+
+ should_create_todo(user: member, target: issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: note)
+ should_create_todo(user: admin, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
+ should_create_todo(user: guest, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
+ end
+
+ it 'creates a directly addressed todo for each valid addressed user' do
+ service.new_note(addressed_note, john_doe)
+
+ should_create_todo(user: member, target: issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note)
+ should_create_todo(user: guest, target: issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note)
+ should_create_todo(user: author, target: issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note)
+ should_create_todo(user: john_doe, target: issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note)
+ should_not_create_todo(user: non_member, target: issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note)
+ end
+
it 'does not create todo if user can not see the issue when leaving a note on a confidential issue' do
service.new_note(note_on_confidential_issue, john_doe)
@@ -287,6 +407,17 @@ describe TodoService, services: true do
should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
end
+ it 'does not create a directly addressed todo if user can not see the issue when leaving a note on a confidential issue' do
+ service.new_note(addressed_note_on_confidential_issue, john_doe)
+
+ should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
+ should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
+ should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
+ should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
+ should_not_create_todo(user: guest, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
+ should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
+ end
+
it 'creates a todo for each valid mentioned user when leaving a note on commit' do
service.new_note(note_on_commit, john_doe)
@@ -296,6 +427,15 @@ describe TodoService, services: true do
should_not_create_todo(user: non_member, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
end
+ it 'creates a directly addressed todo for each valid mentioned user when leaving a note on commit' do
+ service.new_note(addressed_note_on_commit, john_doe)
+
+ should_create_todo(user: member, target_id: nil, target_type: 'Commit', commit_id: addressed_note_on_commit.commit_id, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_commit)
+ should_create_todo(user: author, target_id: nil, target_type: 'Commit', commit_id: addressed_note_on_commit.commit_id, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_commit)
+ should_create_todo(user: john_doe, target_id: nil, target_type: 'Commit', commit_id: addressed_note_on_commit.commit_id, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_commit)
+ should_not_create_todo(user: non_member, target_id: nil, target_type: 'Commit', commit_id: addressed_note_on_commit.commit_id, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_commit)
+ end
+
it 'does not create todo when leaving a note on snippet' do
should_not_create_any_todo { service.new_note(note_on_project_snippet, john_doe) }
end
@@ -324,6 +464,7 @@ describe TodoService, services: true do
describe 'Merge Requests' do
let(:mr_assigned) { create(:merge_request, source_project: project, author: author, assignee: john_doe, description: "- [ ] Task 1\n- [ ] Task 2 #{mentions}") }
+ let(:addressed_mr_assigned) { create(:merge_request, source_project: project, author: author, assignee: john_doe, description: "#{directly_addressed}\n- [ ] Task 1\n- [ ] Task 2") }
let(:mr_unassigned) { create(:merge_request, source_project: project, author: author, assignee: nil) }
describe '#new_merge_request' do
@@ -350,6 +491,25 @@ describe TodoService, services: true do
should_not_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED)
should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED)
end
+
+ it 'creates a todo for each valid user based on the type of mention' do
+ mr_assigned.update(description: directly_addressed_and_mentioned)
+
+ service.new_merge_request(mr_assigned, author)
+
+ should_create_todo(user: member, target: mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: admin, target: mr_assigned, action: Todo::MENTIONED)
+ end
+
+ it 'creates a directly addressed todo for each valid addressed user' do
+ service.new_merge_request(addressed_mr_assigned, author)
+
+ should_create_todo(user: member, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: guest, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: author, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: john_doe, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: non_member, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ end
end
describe '#update_merge_request' do
@@ -363,12 +523,37 @@ describe TodoService, services: true do
should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED)
end
+ it 'creates a todo for each valid user based on the type of mention' do
+ mr_assigned.update(description: directly_addressed_and_mentioned)
+
+ service.update_merge_request(mr_assigned, author)
+
+ should_create_todo(user: member, target: mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: admin, target: mr_assigned, action: Todo::MENTIONED)
+ end
+
+ it 'creates a directly addressed todo for each valid addressed user' do
+ service.update_merge_request(addressed_mr_assigned, author)
+
+ should_create_todo(user: member, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: guest, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: john_doe, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: author, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: non_member, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ end
+
it 'does not create a todo if user was already mentioned' do
create(:todo, :mentioned, user: member, project: project, target: mr_assigned, author: author)
expect { service.update_merge_request(mr_assigned, author) }.not_to change(member.todos, :count)
end
+ it 'does not create a directly addressed todo if user was already mentioned or addressed' do
+ create(:todo, :directly_addressed, user: member, project: project, target: addressed_mr_assigned, author: author)
+
+ expect{ service.update_merge_request(addressed_mr_assigned, author) }.not_to change(member.todos, :count)
+ end
+
context 'with a task list' do
it 'does not create todo when tasks are marked as completed' do
mr_assigned.update(description: "- [x] Task 1\n- [X] Task 2 #{mentions}")
@@ -384,6 +569,20 @@ describe TodoService, services: true do
should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED)
end
+ it 'does not create directly addressed todo when tasks are marked as completed' do
+ addressed_mr_assigned.update(description: "#{directly_addressed}\n- [x] Task 1\n- [X] Task 2")
+
+ service.update_merge_request(addressed_mr_assigned, author)
+
+ should_not_create_todo(user: admin, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: assignee, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: author, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: john_doe, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: member, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: non_member, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: guest, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ end
+
it 'does not raise an error when description not change' do
mr_assigned.update(title: 'Sample')
@@ -436,6 +635,11 @@ describe TodoService, services: true do
service.reassigned_merge_request(mr_assigned, author)
should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED)
end
+
+ it 'does not create a directly addressed todo for guests' do
+ service.reassigned_merge_request(addressed_mr_assigned, author)
+ should_not_create_todo(user: guest, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ end
end
describe '#merge_merge_request' do
@@ -452,6 +656,11 @@ describe TodoService, services: true do
service.merge_merge_request(mr_assigned, john_doe)
should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED)
end
+
+ it 'does not create directly addressed todo for guests' do
+ service.merge_merge_request(addressed_mr_assigned, john_doe)
+ should_not_create_todo(user: guest, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ end
end
describe '#new_award_emoji' do
@@ -509,6 +718,7 @@ describe TodoService, services: true do
describe '#new_note' do
let(:mention) { john_doe.to_reference }
let(:diff_note_on_merge_request) { create(:diff_note_on_merge_request, project: project, noteable: mr_unassigned, author: author, note: "Hey #{mention}") }
+ let(:addressed_diff_note_on_merge_request) { create(:diff_note_on_merge_request, project: project, noteable: mr_unassigned, author: author, note: "#{mention}, hey!") }
let(:legacy_diff_note_on_merge_request) { create(:legacy_diff_note_on_merge_request, project: project, noteable: mr_unassigned, author: author, note: "Hey #{mention}") }
it 'creates a todo for mentioned user on new diff note' do
@@ -517,6 +727,12 @@ describe TodoService, services: true do
should_create_todo(user: john_doe, target: mr_unassigned, author: author, action: Todo::MENTIONED, note: diff_note_on_merge_request)
end
+ it 'creates a directly addressed todo for addressed user on new diff note' do
+ service.new_note(addressed_diff_note_on_merge_request, author)
+
+ should_create_todo(user: john_doe, target: mr_unassigned, author: author, action: Todo::DIRECTLY_ADDRESSED, note: addressed_diff_note_on_merge_request)
+ end
+
it 'creates a todo for mentioned user on legacy diff note' do
service.new_note(legacy_diff_note_on_merge_request, author)
diff --git a/spec/services/users/destroy_spec.rb b/spec/services/users/destroy_spec.rb
new file mode 100644
index 00000000000..922e82445d0
--- /dev/null
+++ b/spec/services/users/destroy_spec.rb
@@ -0,0 +1,130 @@
+require 'spec_helper'
+
+describe Users::DestroyService, services: true do
+ describe "Deletes a user and all their personal projects" do
+ let!(:user) { create(:user) }
+ let!(:admin) { create(:admin) }
+ let!(:namespace) { create(:namespace, owner: user) }
+ let!(:project) { create(:project, namespace: namespace) }
+ let(:service) { described_class.new(admin) }
+
+ context 'no options are given' do
+ it 'deletes the user' do
+ user_data = service.execute(user)
+
+ expect { user_data['email'].to eq(user.email) }
+ expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { Namespace.with_deleted.find(user.namespace.id) }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it 'will delete the project in the near future' do
+ expect_any_instance_of(Projects::DestroyService).to receive(:async_execute).once
+
+ service.execute(user)
+ end
+ end
+
+ context "a deleted user's issues" do
+ let(:project) { create :project }
+
+ before do
+ project.add_developer(user)
+ end
+
+ context "for an issue the user has created" do
+ let!(:issue) { create(:issue, project: project, author: user) }
+
+ before do
+ service.execute(user)
+ end
+
+ it 'does not delete the issue' do
+ expect(Issue.find_by_id(issue.id)).to be_present
+ end
+
+ it 'migrates the issue so that the "Ghost User" is the issue owner' do
+ migrated_issue = Issue.find_by_id(issue.id)
+
+ expect(migrated_issue.author).to eq(User.ghost)
+ end
+
+ it 'blocks the user before migrating issues to the "Ghost User' do
+ expect(user).to be_blocked
+ end
+ end
+
+ context "for an issue the user was assigned to" do
+ let!(:issue) { create(:issue, project: project, assignee: user) }
+
+ before do
+ service.execute(user)
+ end
+
+ it 'does not delete issues the user is assigned to' do
+ expect(Issue.find_by_id(issue.id)).to be_present
+ end
+
+ it 'migrates the issue so that it is "Unassigned"' do
+ migrated_issue = Issue.find_by_id(issue.id)
+
+ expect(migrated_issue.assignee).to be_nil
+ end
+ end
+ end
+
+ context "solo owned groups present" do
+ let(:solo_owned) { create(:group) }
+ let(:member) { create(:group_member) }
+ let(:user) { member.user }
+
+ before do
+ solo_owned.group_members = [member]
+ service.execute(user)
+ end
+
+ it 'does not delete the user' do
+ expect(User.find(user.id)).to eq user
+ end
+ end
+
+ context "deletions with solo owned groups" do
+ let(:solo_owned) { create(:group) }
+ let(:member) { create(:group_member) }
+ let(:user) { member.user }
+
+ before do
+ solo_owned.group_members = [member]
+ service.execute(user, delete_solo_owned_groups: true)
+ end
+
+ it 'deletes solo owned groups' do
+ expect { Project.find(solo_owned.id) }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it 'deletes the user' do
+ expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+
+ context "deletion permission checks" do
+ it 'does not delete the user when user is not an admin' do
+ other_user = create(:user)
+
+ expect { described_class.new(other_user).execute(user) }.to raise_error(Gitlab::Access::AccessDeniedError)
+ expect(User.exists?(user.id)).to be(true)
+ end
+
+ it 'allows admins to delete anyone' do
+ described_class.new(admin).execute(user)
+
+ expect(User.exists?(user.id)).to be(false)
+ end
+
+ it 'allows users to delete their own account' do
+ described_class.new(user).execute(user)
+
+ expect(User.exists?(user.id)).to be(false)
+ end
+ end
+ end
+end
diff --git a/spec/services/wiki_pages/create_service_spec.rb b/spec/services/wiki_pages/create_service_spec.rb
new file mode 100644
index 00000000000..5341ba3d261
--- /dev/null
+++ b/spec/services/wiki_pages/create_service_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+describe WikiPages::CreateService, services: true do
+ let(:project) { create(:empty_project) }
+ let(:user) { create(:user) }
+ let(:opts) do
+ {
+ title: 'Title',
+ content: 'Content for wiki page',
+ format: 'markdown'
+ }
+ end
+ let(:service) { described_class.new(project, user, opts) }
+
+ describe '#execute' do
+ context "valid params" do
+ before do
+ allow(service).to receive(:execute_hooks)
+ project.add_master(user)
+ end
+
+ subject { service.execute }
+
+ it 'creates a valid wiki page' do
+ is_expected.to be_valid
+ expect(subject.title).to eq(opts[:title])
+ expect(subject.content).to eq(opts[:content])
+ expect(subject.format).to eq(opts[:format].to_sym)
+ end
+
+ it 'executes webhooks' do
+ expect(service).to have_received(:execute_hooks).once.with(subject, 'create')
+ end
+ end
+ end
+end
diff --git a/spec/services/wiki_pages/destroy_service_spec.rb b/spec/services/wiki_pages/destroy_service_spec.rb
new file mode 100644
index 00000000000..a4b9a390fe2
--- /dev/null
+++ b/spec/services/wiki_pages/destroy_service_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe WikiPages::DestroyService, services: true do
+ let(:project) { create(:empty_project) }
+ let(:user) { create(:user) }
+ let(:wiki_page) { create(:wiki_page) }
+ let(:service) { described_class.new(project, user) }
+
+ describe '#execute' do
+ before do
+ allow(service).to receive(:execute_hooks)
+ project.add_master(user)
+ end
+
+ it 'executes webhooks' do
+ service.execute(wiki_page)
+
+ expect(service).to have_received(:execute_hooks).once.with(wiki_page, 'delete')
+ end
+ end
+end
diff --git a/spec/services/wiki_pages/update_service_spec.rb b/spec/services/wiki_pages/update_service_spec.rb
new file mode 100644
index 00000000000..2bccca764d7
--- /dev/null
+++ b/spec/services/wiki_pages/update_service_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe WikiPages::UpdateService, services: true do
+ let(:project) { create(:empty_project) }
+ let(:user) { create(:user) }
+ let(:wiki_page) { create(:wiki_page) }
+ let(:opts) do
+ {
+ content: 'New content for wiki page',
+ format: 'markdown',
+ message: 'New wiki message'
+ }
+ end
+ let(:service) { described_class.new(project, user, opts) }
+
+ describe '#execute' do
+ context "valid params" do
+ before do
+ allow(service).to receive(:execute_hooks)
+ project.add_master(user)
+ end
+
+ subject { service.execute(wiki_page) }
+
+ it 'updates the wiki page' do
+ is_expected.to be_valid
+ expect(subject.content).to eq(opts[:content])
+ expect(subject.format).to eq(opts[:format].to_sym)
+ expect(subject.message).to eq(opts[:message])
+ end
+
+ it 'executes webhooks' do
+ expect(service).to have_received(:execute_hooks).once.with(subject, 'update')
+ end
+ end
+ end
+end
diff --git a/spec/support/api/pagination_shared_examples.rb b/spec/support/api/pagination_shared_examples.rb
deleted file mode 100644
index 352a6eeec79..00000000000
--- a/spec/support/api/pagination_shared_examples.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-# Specs for paginated resources.
-#
-# Requires an API request:
-# let(:request) { get api("/projects/#{project.id}/repository/branches", user) }
-shared_examples 'a paginated resources' do
- before do
- # Fires the request
- request
- end
-
- it 'has pagination headers' do
- expect(response.headers).to include('X-Total')
- expect(response.headers).to include('X-Total-Pages')
- expect(response.headers).to include('X-Per-Page')
- expect(response.headers).to include('X-Page')
- expect(response.headers).to include('X-Next-Page')
- expect(response.headers).to include('X-Prev-Page')
- expect(response.headers).to include('Link')
- end
-end
diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb
index 247f0954221..6f31828b825 100644
--- a/spec/support/db_cleaner.rb
+++ b/spec/support/db_cleaner.rb
@@ -3,6 +3,10 @@ RSpec.configure do |config|
DatabaseCleaner.clean_with(:truncation)
end
+ config.append_after(:context) do
+ DatabaseCleaner.clean_with(:truncation)
+ end
+
config.before(:each) do
DatabaseCleaner.strategy = :transaction
end
diff --git a/spec/support/drag_to_helper.rb b/spec/support/drag_to_helper.rb
new file mode 100644
index 00000000000..0c0659d3ecd
--- /dev/null
+++ b/spec/support/drag_to_helper.rb
@@ -0,0 +1,13 @@
+module DragTo
+ def drag_to(list_from_index: 0, from_index: 0, to_index: 0, list_to_index: 0, selector: '', scrollable: 'body')
+ evaluate_script("simulateDrag({scrollable: $('#{scrollable}').get(0), from: {el: $('#{selector}').eq(#{list_from_index}).get(0), index: #{from_index}}, to: {el: $('#{selector}').eq(#{list_to_index}).get(0), index: #{to_index}}});")
+
+ Timeout.timeout(Capybara.default_max_wait_time) do
+ loop until drag_active?
+ end
+ end
+
+ def drag_active?
+ page.evaluate_script('window.SIMULATE_DRAG_ACTIVE').zero?
+ end
+end
diff --git a/spec/support/filtered_search_helpers.rb b/spec/support/filtered_search_helpers.rb
new file mode 100644
index 00000000000..58f6636e680
--- /dev/null
+++ b/spec/support/filtered_search_helpers.rb
@@ -0,0 +1,37 @@
+module FilteredSearchHelpers
+ def filtered_search
+ page.find('.filtered-search')
+ end
+
+ def input_filtered_search(search_term, submit: true)
+ filtered_search.set(search_term)
+
+ if submit
+ filtered_search.send_keys(:enter)
+ end
+ end
+
+ def input_filtered_search_keys(search_term)
+ filtered_search.send_keys(search_term)
+ filtered_search.send_keys(:enter)
+ end
+
+ def expect_filtered_search_input(input)
+ expect(find('.filtered-search').value).to eq(input)
+ end
+
+ def clear_search_field
+ find('.filtered-search-input-container .clear-search').click
+ end
+
+ def reset_filters
+ clear_search_field
+ filtered_search.send_keys(:enter)
+ end
+
+ def init_label_search
+ filtered_search.set('label:')
+ # This ensures the dropdown is shown
+ expect(find('#js-dropdown-label')).not_to have_css('.filter-dropdown-loading')
+ end
+end
diff --git a/spec/support/gitlab_stubs/session.json b/spec/support/gitlab_stubs/session.json
index ce8dfe5ae75..cd55d63125e 100644
--- a/spec/support/gitlab_stubs/session.json
+++ b/spec/support/gitlab_stubs/session.json
@@ -7,7 +7,7 @@
"skype":"aertert",
"linkedin":"",
"twitter":"",
- "theme_id":2,"color_scheme_id":2,
+ "color_scheme_id":2,
"state":"active",
"created_at":"2012-12-21T13:02:20Z",
"extern_uid":null,
@@ -17,4 +17,4 @@
"can_create_project":false,
"private_token":"Wvjy2Krpb7y8xi93owUz",
"access_token":"Wvjy2Krpb7y8xi93owUz"
-} \ No newline at end of file
+}
diff --git a/spec/support/gitlab_stubs/user.json b/spec/support/gitlab_stubs/user.json
index ce8dfe5ae75..cd55d63125e 100644
--- a/spec/support/gitlab_stubs/user.json
+++ b/spec/support/gitlab_stubs/user.json
@@ -7,7 +7,7 @@
"skype":"aertert",
"linkedin":"",
"twitter":"",
- "theme_id":2,"color_scheme_id":2,
+ "color_scheme_id":2,
"state":"active",
"created_at":"2012-12-21T13:02:20Z",
"extern_uid":null,
@@ -17,4 +17,4 @@
"can_create_project":false,
"private_token":"Wvjy2Krpb7y8xi93owUz",
"access_token":"Wvjy2Krpb7y8xi93owUz"
-} \ No newline at end of file
+}
diff --git a/spec/support/issuables_list_metadata_shared_examples.rb b/spec/support/issuables_list_metadata_shared_examples.rb
new file mode 100644
index 00000000000..4c0f556e736
--- /dev/null
+++ b/spec/support/issuables_list_metadata_shared_examples.rb
@@ -0,0 +1,36 @@
+shared_examples 'issuables list meta-data' do |issuable_type, action = nil|
+ before do
+ @issuable_ids = []
+
+ 2.times do
+ issuable =
+ if issuable_type == :issue
+ create(issuable_type, project: project)
+ else
+ create(issuable_type, title: FFaker::Lorem.sentence, source_project: project, source_branch: FFaker::Name.name)
+ end
+
+ @issuable_ids << issuable.id
+
+ issuable.id.times { create(:note, noteable: issuable, project: issuable.project) }
+ (issuable.id + 1).times { create(:award_emoji, :downvote, awardable: issuable) }
+ (issuable.id + 2).times { create(:award_emoji, :upvote, awardable: issuable) }
+ end
+ end
+
+ it "creates indexed meta-data object for issuable notes and votes count" do
+ if action
+ get action
+ else
+ get :index, namespace_id: project.namespace, project_id: project
+ end
+
+ meta_data = assigns(:issuable_meta_data)
+
+ @issuable_ids.each do |id|
+ expect(meta_data[id].notes_count).to eq(id)
+ expect(meta_data[id].downvotes).to eq(id + 1)
+ expect(meta_data[id].upvotes).to eq(id + 2)
+ end
+ end
+end
diff --git a/spec/support/javascript_fixtures_helpers.rb b/spec/support/javascript_fixtures_helpers.rb
index 0b8729db0f9..a982b159b48 100644
--- a/spec/support/javascript_fixtures_helpers.rb
+++ b/spec/support/javascript_fixtures_helpers.rb
@@ -5,7 +5,7 @@ require 'gitlab/popen'
module JavaScriptFixturesHelpers
include Gitlab::Popen
- FIXTURE_PATH = 'spec/javascripts/fixtures'
+ FIXTURE_PATH = 'spec/javascripts/fixtures'.freeze
# Public: Removes all fixture files from given directory
#
diff --git a/spec/support/jira_service_helper.rb b/spec/support/jira_service_helper.rb
index 929fc0c5182..97ae0b6afc5 100644
--- a/spec/support/jira_service_helper.rb
+++ b/spec/support/jira_service_helper.rb
@@ -1,5 +1,5 @@
module JiraServiceHelper
- JIRA_URL = "http://jira.example.net"
+ JIRA_URL = "http://jira.example.net".freeze
JIRA_API = JIRA_URL + "/rest/api/2"
def jira_service_settings
diff --git a/spec/support/kubernetes_helpers.rb b/spec/support/kubernetes_helpers.rb
index 444612cf871..b5ed71ba3be 100644
--- a/spec/support/kubernetes_helpers.rb
+++ b/spec/support/kubernetes_helpers.rb
@@ -2,23 +2,24 @@ module KubernetesHelpers
include Gitlab::Kubernetes
def kube_discovery_body
- { "kind" => "APIResourceList",
+ {
+ "kind" => "APIResourceList",
"resources" => [
{ "name" => "pods", "namespaced" => true, "kind" => "Pod" },
- ],
+ ]
}
end
def kube_pods_body(*pods)
{ "kind" => "PodList",
- "items" => [ kube_pod ],
- }
+ "items" => [kube_pod] }
end
# This is a partial response, it will have many more elements in reality but
# these are the ones we care about at the moment
def kube_pod(app: "valid-pod-label")
- { "metadata" => {
+ {
+ "metadata" => {
"name" => "kube-pod",
"creationTimestamp" => "2016-11-25T19:55:19Z",
"labels" => { "app" => app },
@@ -29,7 +30,7 @@ module KubernetesHelpers
{ "name" => "container-1" },
],
},
- "status" => { "phase" => "Running" },
+ "status" => { "phase" => "Running" }
}
end
diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb
index ad1eed5b369..9ffb00be0b8 100644
--- a/spec/support/login_helpers.rb
+++ b/spec/support/login_helpers.rb
@@ -15,11 +15,12 @@ module LoginHelpers
# user = create(:user)
# login_as(user)
def login_as(user_or_role)
- if user_or_role.kind_of?(User)
- @user = user_or_role
- else
- @user = create(user_or_role)
- end
+ @user =
+ if user_or_role.is_a?(User)
+ user_or_role
+ else
+ create(user_or_role)
+ end
login_with(@user)
end
diff --git a/spec/support/markdown_feature.rb b/spec/support/markdown_feature.rb
index a79386b5db9..dea0015f105 100644
--- a/spec/support/markdown_feature.rb
+++ b/spec/support/markdown_feature.rb
@@ -79,8 +79,8 @@ class MarkdownFeature
def xproject
@xproject ||= begin
- namespace = create(:namespace, name: 'cross-reference')
- create(:project, namespace: namespace) do |project|
+ group = create(:group, :nested)
+ create(:project, namespace: group) do |project|
project.team << [user, :developer]
end
end
diff --git a/spec/support/matchers/access_matchers.rb b/spec/support/matchers/access_matchers.rb
index ceddb656596..7d238850520 100644
--- a/spec/support/matchers/access_matchers.rb
+++ b/spec/support/matchers/access_matchers.rb
@@ -38,7 +38,7 @@ module AccessMatchers
end
def description_for(user, type)
- if user.kind_of?(User)
+ if user.is_a?(User)
# User#inspect displays too much information for RSpec's descriptions
"be #{type} for the specified user"
else
diff --git a/spec/support/matchers/pagination_matcher.rb b/spec/support/matchers/pagination_matcher.rb
new file mode 100644
index 00000000000..60f5e8239a7
--- /dev/null
+++ b/spec/support/matchers/pagination_matcher.rb
@@ -0,0 +1,5 @@
+RSpec::Matchers.define :include_pagination_headers do |expected|
+ match do |actual|
+ expect(actual.headers).to include('X-Total', 'X-Total-Pages', 'X-Per-Page', 'X-Page', 'X-Next-Page', 'X-Prev-Page', 'Link')
+ end
+end
diff --git a/spec/support/matchers/satisfy_matchers.rb b/spec/support/matchers/satisfy_matchers.rb
new file mode 100644
index 00000000000..585915bac93
--- /dev/null
+++ b/spec/support/matchers/satisfy_matchers.rb
@@ -0,0 +1,19 @@
+# These matchers are a syntactic hack to provide more readable expectations for
+# an Enumerable object.
+#
+# They take advantage of the `all?`, `none?`, and `one?` methods, and the fact
+# that RSpec provides a `be_something` matcher for all predicates.
+#
+# Example:
+#
+# # Ensure exactly one object in an Array satisfies a condition
+# expect(users.one? { |u| u.admin? }).to eq true
+#
+# # The same thing, but using the `be_one` matcher
+# expect(users).to be_one { |u| u.admin? }
+#
+# # The same thing again, but using `satisfy_one` for improved readability
+# expect(users).to satisfy_one { |u| u.admin? }
+RSpec::Matchers.alias_matcher :satisfy_all, :be_all
+RSpec::Matchers.alias_matcher :satisfy_none, :be_none
+RSpec::Matchers.alias_matcher :satisfy_one, :be_one
diff --git a/spec/support/merge_request_helpers.rb b/spec/support/merge_request_helpers.rb
index d5801c8272f..326b85eabd0 100644
--- a/spec/support/merge_request_helpers.rb
+++ b/spec/support/merge_request_helpers.rb
@@ -10,4 +10,13 @@ module MergeRequestHelpers
def last_merge_request
page.all('ul.mr-list > li').last.text
end
+
+ def expect_mr_list_count(open_count, closed_count = 0)
+ all_count = open_count + closed_count
+
+ expect(page).to have_issuable_counts(open: open_count, closed: closed_count, all: all_count)
+ page.within '.mr-list' do
+ expect(page).to have_selector('.merge-request', count: open_count)
+ end
+ end
end
diff --git a/spec/support/repo_helpers.rb b/spec/support/repo_helpers.rb
index 73f375c481b..e9d5c7b12ae 100644
--- a/spec/support/repo_helpers.rb
+++ b/spec/support/repo_helpers.rb
@@ -42,7 +42,7 @@ Signed-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>
eos
)
end
-
+
def another_sample_commit
OpenStruct.new(
id: "e56497bb5f03a90a51293fc6d516788730953899",
@@ -100,13 +100,13 @@ eos
}
]
- commits = [
- '5937ac0a7beb003549fc5fd26fc247adbce4a52e',
- '570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
- '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9',
- 'd14d6c0abdd253381df51a723d58691b2ee1ab08',
- 'c1acaa58bbcbc3eafe538cb8274ba387047b69f8',
- ].reverse # last commit is recent one
+ commits = %w(
+ 5937ac0a7beb003549fc5fd26fc247adbce4a52e
+ 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
+ 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ d14d6c0abdd253381df51a723d58691b2ee1ab08
+ c1acaa58bbcbc3eafe538cb8274ba387047b69f8
+ ).reverse # last commit is recent one
OpenStruct.new(
source_branch: 'master',
diff --git a/spec/support/seed_helper.rb b/spec/support/seed_helper.rb
index 03fa0a66b9a..07f81e9c4f3 100644
--- a/spec/support/seed_helper.rb
+++ b/spec/support/seed_helper.rb
@@ -7,7 +7,7 @@ TEST_MUTABLE_REPO_PATH = File.join(SEED_REPOSITORY_PATH, "mutable-repo.git")
TEST_BROKEN_REPO_PATH = File.join(SEED_REPOSITORY_PATH, "broken-repo.git")
module SeedHelper
- GITLAB_URL = "https://gitlab.com/gitlab-org/gitlab-git-test.git"
+ GITLAB_URL = "https://gitlab.com/gitlab-org/gitlab-git-test.git".freeze
def ensure_seeds
if File.exist?(SEED_REPOSITORY_PATH)
diff --git a/spec/support/seed_repo.rb b/spec/support/seed_repo.rb
index 9f2cd7c67c5..99a500bbbb1 100644
--- a/spec/support/seed_repo.rb
+++ b/spec/support/seed_repo.rb
@@ -25,64 +25,64 @@
module SeedRepo
module BigCommit
- ID = "913c66a37b4a45b9769037c55c2d238bd0942d2e"
- PARENT_ID = "cfe32cf61b73a0d5e9f13e774abde7ff789b1660"
- MESSAGE = "Files, encoding and much more"
- AUTHOR_FULL_NAME = "Dmitriy Zaporozhets"
+ ID = "913c66a37b4a45b9769037c55c2d238bd0942d2e".freeze
+ PARENT_ID = "cfe32cf61b73a0d5e9f13e774abde7ff789b1660".freeze
+ MESSAGE = "Files, encoding and much more".freeze
+ AUTHOR_FULL_NAME = "Dmitriy Zaporozhets".freeze
FILES_COUNT = 2
end
module Commit
- ID = "570e7b2abdd848b95f2f578043fc23bd6f6fd24d"
- PARENT_ID = "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9"
- MESSAGE = "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n"
- AUTHOR_FULL_NAME = "Dmitriy Zaporozhets"
- FILES = ["files/ruby/popen.rb", "files/ruby/regex.rb"]
+ ID = "570e7b2abdd848b95f2f578043fc23bd6f6fd24d".freeze
+ PARENT_ID = "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9".freeze
+ MESSAGE = "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n".freeze
+ AUTHOR_FULL_NAME = "Dmitriy Zaporozhets".freeze
+ FILES = ["files/ruby/popen.rb", "files/ruby/regex.rb"].freeze
FILES_COUNT = 2
- C_FILE_PATH = "files/ruby"
- C_FILES = ["popen.rb", "regex.rb", "version_info.rb"]
- BLOB_FILE = %{%h3= @key.title\n%hr\n%pre= @key.key\n.actions\n = link_to 'Remove', @key, :confirm => 'Are you sure?', :method => :delete, :class => \"btn danger delete-key\"\n\n\n}
- BLOB_FILE_PATH = "app/views/keys/show.html.haml"
+ C_FILE_PATH = "files/ruby".freeze
+ C_FILES = ["popen.rb", "regex.rb", "version_info.rb"].freeze
+ BLOB_FILE = %{%h3= @key.title\n%hr\n%pre= @key.key\n.actions\n = link_to 'Remove', @key, :confirm => 'Are you sure?', :method => :delete, :class => \"btn danger delete-key\"\n\n\n}.freeze
+ BLOB_FILE_PATH = "app/views/keys/show.html.haml".freeze
end
module EmptyCommit
- ID = "b0e52af38d7ea43cf41d8a6f2471351ac036d6c9"
- PARENT_ID = "40f4a7a617393735a95a0bb67b08385bc1e7c66d"
- MESSAGE = "Empty commit"
- AUTHOR_FULL_NAME = "Rémy Coutable"
- FILES = []
+ ID = "b0e52af38d7ea43cf41d8a6f2471351ac036d6c9".freeze
+ PARENT_ID = "40f4a7a617393735a95a0bb67b08385bc1e7c66d".freeze
+ MESSAGE = "Empty commit".freeze
+ AUTHOR_FULL_NAME = "Rémy Coutable".freeze
+ FILES = [].freeze
FILES_COUNT = FILES.count
end
module EncodingCommit
- ID = "40f4a7a617393735a95a0bb67b08385bc1e7c66d"
- PARENT_ID = "66028349a123e695b589e09a36634d976edcc5e8"
- MESSAGE = "Add ISO-8859-encoded file"
- AUTHOR_FULL_NAME = "Stan Hu"
- FILES = ["encoding/iso8859.txt"]
+ ID = "40f4a7a617393735a95a0bb67b08385bc1e7c66d".freeze
+ PARENT_ID = "66028349a123e695b589e09a36634d976edcc5e8".freeze
+ MESSAGE = "Add ISO-8859-encoded file".freeze
+ AUTHOR_FULL_NAME = "Stan Hu".freeze
+ FILES = ["encoding/iso8859.txt"].freeze
FILES_COUNT = FILES.count
end
module FirstCommit
- ID = "1a0b36b3cdad1d2ee32457c102a8c0b7056fa863"
+ ID = "1a0b36b3cdad1d2ee32457c102a8c0b7056fa863".freeze
PARENT_ID = nil
- MESSAGE = "Initial commit"
- AUTHOR_FULL_NAME = "Dmitriy Zaporozhets"
- FILES = ["LICENSE", ".gitignore", "README.md"]
+ MESSAGE = "Initial commit".freeze
+ AUTHOR_FULL_NAME = "Dmitriy Zaporozhets".freeze
+ FILES = ["LICENSE", ".gitignore", "README.md"].freeze
FILES_COUNT = 3
end
module LastCommit
- ID = "4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6"
- PARENT_ID = "0e1b353b348f8477bdbec1ef47087171c5032cd9"
- MESSAGE = "Merge branch 'master' into 'master'"
- AUTHOR_FULL_NAME = "Stan Hu"
- FILES = ["bin/executable"]
+ ID = "4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6".freeze
+ PARENT_ID = "0e1b353b348f8477bdbec1ef47087171c5032cd9".freeze
+ MESSAGE = "Merge branch 'master' into 'master'".freeze
+ AUTHOR_FULL_NAME = "Stan Hu".freeze
+ FILES = ["bin/executable"].freeze
FILES_COUNT = FILES.count
end
module Repo
- HEAD = "master"
+ HEAD = "master".freeze
BRANCHES = %w[
feature
fix
@@ -93,14 +93,14 @@ module SeedRepo
gitattributes-updated
master
merge-test
- ]
- TAGS = %w[v1.0.0 v1.1.0 v1.2.0 v1.2.1]
+ ].freeze
+ TAGS = %w[v1.0.0 v1.1.0 v1.2.0 v1.2.1].freeze
end
module RubyBlob
- ID = "7e3e39ebb9b2bf433b4ad17313770fbe4051649c"
- NAME = "popen.rb"
- CONTENT = <<-eos
+ ID = "7e3e39ebb9b2bf433b4ad17313770fbe4051649c".freeze
+ NAME = "popen.rb".freeze
+ CONTENT = <<-eos.freeze
require 'fileutils'
require 'open3'
diff --git a/spec/support/select2_helper.rb b/spec/support/select2_helper.rb
index d30cc8ff9f2..0d526045012 100644
--- a/spec/support/select2_helper.rb
+++ b/spec/support/select2_helper.rb
@@ -12,7 +12,7 @@
module Select2Helper
def select2(value, options = {})
- raise ArgumentError, 'options must be a Hash' unless options.kind_of?(Hash)
+ raise ArgumentError, 'options must be a Hash' unless options.is_a?(Hash)
selector = options.fetch(:from)
diff --git a/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb b/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb
index dd54b0addda..81d06dc2a3d 100644
--- a/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb
+++ b/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb
@@ -11,7 +11,7 @@ shared_examples 'new issuable record that supports slash commands' do
let(:params) { base_params.merge(defined?(default_params) ? default_params : {}).merge(example_params) }
let(:issuable) { described_class.new(project, user, params).execute }
- before { project.team << [assignee, :master ] }
+ before { project.team << [assignee, :master] }
context 'with labels in command only' do
let(:example_params) do
@@ -58,7 +58,7 @@ shared_examples 'new issuable record that supports slash commands' do
let(:example_params) do
{
assignee: create(:user),
- milestone_id: double(:milestone),
+ milestone_id: 1,
description: %(/assign @#{assignee.username}\n/milestone %"#{milestone.name}")
}
end
diff --git a/spec/support/slash_commands_helpers.rb b/spec/support/slash_commands_helpers.rb
index df483afa0e3..0d91fe5fd5d 100644
--- a/spec/support/slash_commands_helpers.rb
+++ b/spec/support/slash_commands_helpers.rb
@@ -3,7 +3,7 @@ module SlashCommandsHelpers
Sidekiq::Testing.fake! do
page.within('.js-main-target-form') do
fill_in 'note[note]', with: text
- click_button 'Comment'
+ find('.comment-btn').trigger('click')
end
end
end
diff --git a/spec/support/stub_gitlab_calls.rb b/spec/support/stub_gitlab_calls.rb
index 93f96cacc00..a01ef576234 100644
--- a/spec/support/stub_gitlab_calls.rb
+++ b/spec/support/stub_gitlab_calls.rb
@@ -35,7 +35,7 @@ module StubGitlabCalls
{ "tags" => tags }
)
allow_any_instance_of(ContainerRegistry::Client).to receive(:repository_manifest).and_return(
- JSON.load(File.read(Rails.root + 'spec/fixtures/container_registry/tag_manifest.json'))
+ JSON.parse(File.read(Rails.root + 'spec/fixtures/container_registry/tag_manifest.json'))
)
allow_any_instance_of(ContainerRegistry::Client).to receive(:blob).and_return(
File.read(Rails.root + 'spec/fixtures/container_registry/config_blob.json')
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index b87232a350b..c3aa3ef44c2 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -38,7 +38,7 @@ module TestEnv
'deleted-image-test' => '6c17798',
'wip' => 'b9238ee',
'csv' => '3dd0896'
- }
+ }.freeze
# gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
# need to keep all the branches in sync.
@@ -48,7 +48,7 @@ module TestEnv
'master' => '5937ac0',
'remove-submodule' => '2a33e0c',
'conflict-resolvable-fork' => '404fa3f'
- }
+ }.freeze
# Test environment
#
@@ -135,7 +135,7 @@ module TestEnv
def copy_repo(project)
base_repo_path = File.expand_path(factory_repo_path_bare)
- target_repo_path = File.expand_path(project.repository_storage_path + "/#{project.namespace.path}/#{project.path}.git")
+ target_repo_path = File.expand_path(project.repository_storage_path + "/#{project.full_path}.git")
FileUtils.mkdir_p(target_repo_path)
FileUtils.cp_r("#{base_repo_path}/.", target_repo_path)
FileUtils.chmod_R 0755, target_repo_path
@@ -152,7 +152,7 @@ module TestEnv
def copy_forked_repo_with_submodules(project)
base_repo_path = File.expand_path(forked_repo_path_bare)
- target_repo_path = File.expand_path(project.repository_storage_path + "/#{project.namespace.path}/#{project.path}.git")
+ target_repo_path = File.expand_path(project.repository_storage_path + "/#{project.full_path}.git")
FileUtils.mkdir_p(target_repo_path)
FileUtils.cp_r("#{base_repo_path}/.", target_repo_path)
FileUtils.chmod_R 0755, target_repo_path
diff --git a/spec/support/time_tracking_shared_examples.rb b/spec/support/time_tracking_shared_examples.rb
index 02657684b57..52f4fabdc47 100644
--- a/spec/support/time_tracking_shared_examples.rb
+++ b/spec/support/time_tracking_shared_examples.rb
@@ -77,6 +77,6 @@ end
def submit_time(slash_command)
fill_in 'note[note]', with: slash_command
- click_button 'Comment'
+ find('.comment-btn').trigger('click')
wait_for_ajax
end
diff --git a/spec/views/projects/_home_panel.html.haml_spec.rb b/spec/views/projects/_home_panel.html.haml_spec.rb
new file mode 100644
index 00000000000..f5381a48207
--- /dev/null
+++ b/spec/views/projects/_home_panel.html.haml_spec.rb
@@ -0,0 +1,38 @@
+require 'spec_helper'
+
+describe 'projects/_home_panel', :view do
+ let(:project) { create(:empty_project, :public) }
+
+ let(:notification_settings) do
+ user&.notification_settings_for(project)
+ end
+
+ before do
+ assign(:project, project)
+ assign(:notification_setting, notification_settings)
+
+ allow(view).to receive(:current_user).and_return(user)
+ allow(view).to receive(:can?).and_return(false)
+ end
+
+ context 'when user is signed in' do
+ let(:user) { create(:user) }
+
+ it 'makes it possible to set notification level' do
+ render
+
+ expect(view).to render_template('shared/notifications/_button')
+ expect(rendered).to have_selector('.notification-dropdown')
+ end
+ end
+
+ context 'when user is signed out' do
+ let(:user) { nil }
+
+ it 'is not possible to set notification level' do
+ render
+
+ expect(rendered).not_to have_selector('.notification_dropdown')
+ end
+ end
+end
diff --git a/spec/views/projects/notes/_form.html.haml_spec.rb b/spec/views/projects/notes/_form.html.haml_spec.rb
index b14b1ece2d0..b61f016967f 100644
--- a/spec/views/projects/notes/_form.html.haml_spec.rb
+++ b/spec/views/projects/notes/_form.html.haml_spec.rb
@@ -21,7 +21,7 @@ describe 'projects/notes/_form' do
let(:note) { build(:"note_on_#{noteable}", project: project) }
it 'says that only markdown is supported, not slash commands' do
- expect(rendered).to have_content('Styling with Markdown and slash commands are supported')
+ expect(rendered).to have_content('Markdown and slash commands are supported')
end
end
end
@@ -30,7 +30,7 @@ describe 'projects/notes/_form' do
let(:note) { build(:note_on_commit, project: project) }
it 'says that only markdown is supported, not slash commands' do
- expect(rendered).to have_content('Styling with Markdown is supported')
+ expect(rendered).to have_content('Markdown is supported')
end
end
end
diff --git a/spec/workers/delete_user_worker_spec.rb b/spec/workers/delete_user_worker_spec.rb
index 14c56521280..0765573408c 100644
--- a/spec/workers/delete_user_worker_spec.rb
+++ b/spec/workers/delete_user_worker_spec.rb
@@ -5,14 +5,14 @@ describe DeleteUserWorker do
let!(:current_user) { create(:user) }
it "calls the DeleteUserWorker with the params it was given" do
- expect_any_instance_of(DeleteUserService).to receive(:execute).
+ expect_any_instance_of(Users::DestroyService).to receive(:execute).
with(user, {})
DeleteUserWorker.new.perform(current_user.id, user.id)
end
it "uses symbolized keys" do
- expect_any_instance_of(DeleteUserService).to receive(:execute).
+ expect_any_instance_of(Users::DestroyService).to receive(:execute).
with(user, test: "test")
DeleteUserWorker.new.perform(current_user.id, user.id, "test" => "test")
diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb
index 5ef8cf1105b..a60af574a08 100644
--- a/spec/workers/git_garbage_collect_worker_spec.rb
+++ b/spec/workers/git_garbage_collect_worker_spec.rb
@@ -102,8 +102,8 @@ describe GitGarbageCollectWorker do
new_commit_sha = Rugged::Commit.create(
rugged,
message: "hello world #{SecureRandom.hex(6)}",
- author: Gitlab::Git::committer_hash(email: 'foo@bar', name: 'baz'),
- committer: Gitlab::Git::committer_hash(email: 'foo@bar', name: 'baz'),
+ author: Gitlab::Git.committer_hash(email: 'foo@bar', name: 'baz'),
+ committer: Gitlab::Git.committer_hash(email: 'foo@bar', name: 'baz'),
tree: old_commit.tree,
parents: [old_commit],
)
diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb
index 60605460adb..87521ae408e 100644
--- a/spec/workers/repository_fork_worker_spec.rb
+++ b/spec/workers/repository_fork_worker_spec.rb
@@ -15,24 +15,24 @@ describe RepositoryForkWorker do
it "creates a new repository from a fork" do
expect(shell).to receive(:fork_repository).with(
'/test/path',
- project.path_with_namespace,
+ project.full_path,
project.repository_storage_path,
- fork_project.namespace.path
+ fork_project.namespace.full_path
).and_return(true)
subject.perform(
project.id,
'/test/path',
- project.path_with_namespace,
- fork_project.namespace.path)
+ project.full_path,
+ fork_project.namespace.full_path)
end
it 'flushes various caches' do
expect(shell).to receive(:fork_repository).with(
'/test/path',
- project.path_with_namespace,
+ project.full_path,
project.repository_storage_path,
- fork_project.namespace.path
+ fork_project.namespace.full_path
).and_return(true)
expect_any_instance_of(Repository).to receive(:expire_emptiness_caches).
@@ -41,8 +41,8 @@ describe RepositoryForkWorker do
expect_any_instance_of(Repository).to receive(:expire_exists_cache).
and_call_original
- subject.perform(project.id, '/test/path', project.path_with_namespace,
- fork_project.namespace.path)
+ subject.perform(project.id, '/test/path', project.full_path,
+ fork_project.namespace.full_path)
end
it "handles bad fork" do
@@ -53,8 +53,8 @@ describe RepositoryForkWorker do
subject.perform(
project.id,
'/test/path',
- project.path_with_namespace,
- fork_project.namespace.path)
+ project.full_path,
+ fork_project.namespace.full_path)
end
end
end
diff --git a/spec/workers/repository_import_worker_spec.rb b/spec/workers/repository_import_worker_spec.rb
index f1b1574abf4..c42f3147b7a 100644
--- a/spec/workers/repository_import_worker_spec.rb
+++ b/spec/workers/repository_import_worker_spec.rb
@@ -20,7 +20,7 @@ describe RepositoryImportWorker do
context 'when the import has failed' do
it 'hide the credentials that were used in the import URL' do
- error = %Q{remote: Not Found fatal: repository 'https://user:pass@test.com/root/repoC.git/' not found }
+ error = %q{remote: Not Found fatal: repository 'https://user:pass@test.com/root/repoC.git/' not found }
expect_any_instance_of(Projects::ImportService).to receive(:execute).
and_return({ status: :error, message: error })
diff --git a/vendor/assets/javascripts/es6-promise.auto.js b/vendor/assets/javascripts/es6-promise.auto.js
deleted file mode 100644
index b8887115a37..00000000000
--- a/vendor/assets/javascripts/es6-promise.auto.js
+++ /dev/null
@@ -1,1156 +0,0 @@
-/*!
- * @overview es6-promise - a tiny implementation of Promises/A+.
- * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
- * @license Licensed under MIT license
- * See https://raw.githubusercontent.com/stefanpenner/es6-promise/master/LICENSE
- * @version 4.0.5
- */
-
-(function (global, factory) {
- typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
- typeof define === 'function' && define.amd ? define(factory) :
- (global.ES6Promise = factory());
-}(this, (function () { 'use strict';
-
-function objectOrFunction(x) {
- return typeof x === 'function' || typeof x === 'object' && x !== null;
-}
-
-function isFunction(x) {
- return typeof x === 'function';
-}
-
-var _isArray = undefined;
-if (!Array.isArray) {
- _isArray = function (x) {
- return Object.prototype.toString.call(x) === '[object Array]';
- };
-} else {
- _isArray = Array.isArray;
-}
-
-var isArray = _isArray;
-
-var len = 0;
-var vertxNext = undefined;
-var customSchedulerFn = undefined;
-
-var asap = function asap(callback, arg) {
- queue[len] = callback;
- queue[len + 1] = arg;
- len += 2;
- if (len === 2) {
- // If len is 2, that means that we need to schedule an async flush.
- // If additional callbacks are queued before the queue is flushed, they
- // will be processed by this flush that we are scheduling.
- if (customSchedulerFn) {
- customSchedulerFn(flush);
- } else {
- scheduleFlush();
- }
- }
-};
-
-function setScheduler(scheduleFn) {
- customSchedulerFn = scheduleFn;
-}
-
-function setAsap(asapFn) {
- asap = asapFn;
-}
-
-var browserWindow = typeof window !== 'undefined' ? window : undefined;
-var browserGlobal = browserWindow || {};
-var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
-var isNode = typeof self === 'undefined' && typeof process !== 'undefined' && ({}).toString.call(process) === '[object process]';
-
-// test for web worker but not in IE10
-var isWorker = typeof Uint8ClampedArray !== 'undefined' && typeof importScripts !== 'undefined' && typeof MessageChannel !== 'undefined';
-
-// node
-function useNextTick() {
- // node version 0.10.x displays a deprecation warning when nextTick is used recursively
- // see https://github.com/cujojs/when/issues/410 for details
- return function () {
- return process.nextTick(flush);
- };
-}
-
-// vertx
-function useVertxTimer() {
- if (typeof vertxNext !== 'undefined') {
- return function () {
- vertxNext(flush);
- };
- }
-
- return useSetTimeout();
-}
-
-function useMutationObserver() {
- var iterations = 0;
- var observer = new BrowserMutationObserver(flush);
- var node = document.createTextNode('');
- observer.observe(node, { characterData: true });
-
- return function () {
- node.data = iterations = ++iterations % 2;
- };
-}
-
-// web worker
-function useMessageChannel() {
- var channel = new MessageChannel();
- channel.port1.onmessage = flush;
- return function () {
- return channel.port2.postMessage(0);
- };
-}
-
-function useSetTimeout() {
- // Store setTimeout reference so es6-promise will be unaffected by
- // other code modifying setTimeout (like sinon.useFakeTimers())
- var globalSetTimeout = setTimeout;
- return function () {
- return globalSetTimeout(flush, 1);
- };
-}
-
-var queue = new Array(1000);
-function flush() {
- for (var i = 0; i < len; i += 2) {
- var callback = queue[i];
- var arg = queue[i + 1];
-
- callback(arg);
-
- queue[i] = undefined;
- queue[i + 1] = undefined;
- }
-
- len = 0;
-}
-
-function attemptVertx() {
- try {
- var r = require;
- var vertx = r('vertx');
- vertxNext = vertx.runOnLoop || vertx.runOnContext;
- return useVertxTimer();
- } catch (e) {
- return useSetTimeout();
- }
-}
-
-var scheduleFlush = undefined;
-// Decide what async method to use to triggering processing of queued callbacks:
-if (isNode) {
- scheduleFlush = useNextTick();
-} else if (BrowserMutationObserver) {
- scheduleFlush = useMutationObserver();
-} else if (isWorker) {
- scheduleFlush = useMessageChannel();
-} else if (browserWindow === undefined && typeof require === 'function') {
- scheduleFlush = attemptVertx();
-} else {
- scheduleFlush = useSetTimeout();
-}
-
-function then(onFulfillment, onRejection) {
- var _arguments = arguments;
-
- var parent = this;
-
- var child = new this.constructor(noop);
-
- if (child[PROMISE_ID] === undefined) {
- makePromise(child);
- }
-
- var _state = parent._state;
-
- if (_state) {
- (function () {
- var callback = _arguments[_state - 1];
- asap(function () {
- return invokeCallback(_state, child, callback, parent._result);
- });
- })();
- } else {
- subscribe(parent, child, onFulfillment, onRejection);
- }
-
- return child;
-}
-
-/**
- `Promise.resolve` returns a promise that will become resolved with the
- passed `value`. It is shorthand for the following:
-
- ```javascript
- let promise = new Promise(function(resolve, reject){
- resolve(1);
- });
-
- promise.then(function(value){
- // value === 1
- });
- ```
-
- Instead of writing the above, your code now simply becomes the following:
-
- ```javascript
- let promise = Promise.resolve(1);
-
- promise.then(function(value){
- // value === 1
- });
- ```
-
- @method resolve
- @static
- @param {Any} value value that the returned promise will be resolved with
- Useful for tooling.
- @return {Promise} a promise that will become fulfilled with the given
- `value`
-*/
-function resolve(object) {
- /*jshint validthis:true */
- var Constructor = this;
-
- if (object && typeof object === 'object' && object.constructor === Constructor) {
- return object;
- }
-
- var promise = new Constructor(noop);
- _resolve(promise, object);
- return promise;
-}
-
-var PROMISE_ID = Math.random().toString(36).substring(16);
-
-function noop() {}
-
-var PENDING = void 0;
-var FULFILLED = 1;
-var REJECTED = 2;
-
-var GET_THEN_ERROR = new ErrorObject();
-
-function selfFulfillment() {
- return new TypeError("You cannot resolve a promise with itself");
-}
-
-function cannotReturnOwn() {
- return new TypeError('A promises callback cannot return that same promise.');
-}
-
-function getThen(promise) {
- try {
- return promise.then;
- } catch (error) {
- GET_THEN_ERROR.error = error;
- return GET_THEN_ERROR;
- }
-}
-
-function tryThen(then, value, fulfillmentHandler, rejectionHandler) {
- try {
- then.call(value, fulfillmentHandler, rejectionHandler);
- } catch (e) {
- return e;
- }
-}
-
-function handleForeignThenable(promise, thenable, then) {
- asap(function (promise) {
- var sealed = false;
- var error = tryThen(then, thenable, function (value) {
- if (sealed) {
- return;
- }
- sealed = true;
- if (thenable !== value) {
- _resolve(promise, value);
- } else {
- fulfill(promise, value);
- }
- }, function (reason) {
- if (sealed) {
- return;
- }
- sealed = true;
-
- _reject(promise, reason);
- }, 'Settle: ' + (promise._label || ' unknown promise'));
-
- if (!sealed && error) {
- sealed = true;
- _reject(promise, error);
- }
- }, promise);
-}
-
-function handleOwnThenable(promise, thenable) {
- if (thenable._state === FULFILLED) {
- fulfill(promise, thenable._result);
- } else if (thenable._state === REJECTED) {
- _reject(promise, thenable._result);
- } else {
- subscribe(thenable, undefined, function (value) {
- return _resolve(promise, value);
- }, function (reason) {
- return _reject(promise, reason);
- });
- }
-}
-
-function handleMaybeThenable(promise, maybeThenable, then$$) {
- if (maybeThenable.constructor === promise.constructor && then$$ === then && maybeThenable.constructor.resolve === resolve) {
- handleOwnThenable(promise, maybeThenable);
- } else {
- if (then$$ === GET_THEN_ERROR) {
- _reject(promise, GET_THEN_ERROR.error);
- } else if (then$$ === undefined) {
- fulfill(promise, maybeThenable);
- } else if (isFunction(then$$)) {
- handleForeignThenable(promise, maybeThenable, then$$);
- } else {
- fulfill(promise, maybeThenable);
- }
- }
-}
-
-function _resolve(promise, value) {
- if (promise === value) {
- _reject(promise, selfFulfillment());
- } else if (objectOrFunction(value)) {
- handleMaybeThenable(promise, value, getThen(value));
- } else {
- fulfill(promise, value);
- }
-}
-
-function publishRejection(promise) {
- if (promise._onerror) {
- promise._onerror(promise._result);
- }
-
- publish(promise);
-}
-
-function fulfill(promise, value) {
- if (promise._state !== PENDING) {
- return;
- }
-
- promise._result = value;
- promise._state = FULFILLED;
-
- if (promise._subscribers.length !== 0) {
- asap(publish, promise);
- }
-}
-
-function _reject(promise, reason) {
- if (promise._state !== PENDING) {
- return;
- }
- promise._state = REJECTED;
- promise._result = reason;
-
- asap(publishRejection, promise);
-}
-
-function subscribe(parent, child, onFulfillment, onRejection) {
- var _subscribers = parent._subscribers;
- var length = _subscribers.length;
-
- parent._onerror = null;
-
- _subscribers[length] = child;
- _subscribers[length + FULFILLED] = onFulfillment;
- _subscribers[length + REJECTED] = onRejection;
-
- if (length === 0 && parent._state) {
- asap(publish, parent);
- }
-}
-
-function publish(promise) {
- var subscribers = promise._subscribers;
- var settled = promise._state;
-
- if (subscribers.length === 0) {
- return;
- }
-
- var child = undefined,
- callback = undefined,
- detail = promise._result;
-
- for (var i = 0; i < subscribers.length; i += 3) {
- child = subscribers[i];
- callback = subscribers[i + settled];
-
- if (child) {
- invokeCallback(settled, child, callback, detail);
- } else {
- callback(detail);
- }
- }
-
- promise._subscribers.length = 0;
-}
-
-function ErrorObject() {
- this.error = null;
-}
-
-var TRY_CATCH_ERROR = new ErrorObject();
-
-function tryCatch(callback, detail) {
- try {
- return callback(detail);
- } catch (e) {
- TRY_CATCH_ERROR.error = e;
- return TRY_CATCH_ERROR;
- }
-}
-
-function invokeCallback(settled, promise, callback, detail) {
- var hasCallback = isFunction(callback),
- value = undefined,
- error = undefined,
- succeeded = undefined,
- failed = undefined;
-
- if (hasCallback) {
- value = tryCatch(callback, detail);
-
- if (value === TRY_CATCH_ERROR) {
- failed = true;
- error = value.error;
- value = null;
- } else {
- succeeded = true;
- }
-
- if (promise === value) {
- _reject(promise, cannotReturnOwn());
- return;
- }
- } else {
- value = detail;
- succeeded = true;
- }
-
- if (promise._state !== PENDING) {
- // noop
- } else if (hasCallback && succeeded) {
- _resolve(promise, value);
- } else if (failed) {
- _reject(promise, error);
- } else if (settled === FULFILLED) {
- fulfill(promise, value);
- } else if (settled === REJECTED) {
- _reject(promise, value);
- }
-}
-
-function initializePromise(promise, resolver) {
- try {
- resolver(function resolvePromise(value) {
- _resolve(promise, value);
- }, function rejectPromise(reason) {
- _reject(promise, reason);
- });
- } catch (e) {
- _reject(promise, e);
- }
-}
-
-var id = 0;
-function nextId() {
- return id++;
-}
-
-function makePromise(promise) {
- promise[PROMISE_ID] = id++;
- promise._state = undefined;
- promise._result = undefined;
- promise._subscribers = [];
-}
-
-function Enumerator(Constructor, input) {
- this._instanceConstructor = Constructor;
- this.promise = new Constructor(noop);
-
- if (!this.promise[PROMISE_ID]) {
- makePromise(this.promise);
- }
-
- if (isArray(input)) {
- this._input = input;
- this.length = input.length;
- this._remaining = input.length;
-
- this._result = new Array(this.length);
-
- if (this.length === 0) {
- fulfill(this.promise, this._result);
- } else {
- this.length = this.length || 0;
- this._enumerate();
- if (this._remaining === 0) {
- fulfill(this.promise, this._result);
- }
- }
- } else {
- _reject(this.promise, validationError());
- }
-}
-
-function validationError() {
- return new Error('Array Methods must be provided an Array');
-};
-
-Enumerator.prototype._enumerate = function () {
- var length = this.length;
- var _input = this._input;
-
- for (var i = 0; this._state === PENDING && i < length; i++) {
- this._eachEntry(_input[i], i);
- }
-};
-
-Enumerator.prototype._eachEntry = function (entry, i) {
- var c = this._instanceConstructor;
- var resolve$$ = c.resolve;
-
- if (resolve$$ === resolve) {
- var _then = getThen(entry);
-
- if (_then === then && entry._state !== PENDING) {
- this._settledAt(entry._state, i, entry._result);
- } else if (typeof _then !== 'function') {
- this._remaining--;
- this._result[i] = entry;
- } else if (c === Promise) {
- var promise = new c(noop);
- handleMaybeThenable(promise, entry, _then);
- this._willSettleAt(promise, i);
- } else {
- this._willSettleAt(new c(function (resolve$$) {
- return resolve$$(entry);
- }), i);
- }
- } else {
- this._willSettleAt(resolve$$(entry), i);
- }
-};
-
-Enumerator.prototype._settledAt = function (state, i, value) {
- var promise = this.promise;
-
- if (promise._state === PENDING) {
- this._remaining--;
-
- if (state === REJECTED) {
- _reject(promise, value);
- } else {
- this._result[i] = value;
- }
- }
-
- if (this._remaining === 0) {
- fulfill(promise, this._result);
- }
-};
-
-Enumerator.prototype._willSettleAt = function (promise, i) {
- var enumerator = this;
-
- subscribe(promise, undefined, function (value) {
- return enumerator._settledAt(FULFILLED, i, value);
- }, function (reason) {
- return enumerator._settledAt(REJECTED, i, reason);
- });
-};
-
-/**
- `Promise.all` accepts an array of promises, and returns a new promise which
- is fulfilled with an array of fulfillment values for the passed promises, or
- rejected with the reason of the first passed promise to be rejected. It casts all
- elements of the passed iterable to promises as it runs this algorithm.
-
- Example:
-
- ```javascript
- let promise1 = resolve(1);
- let promise2 = resolve(2);
- let promise3 = resolve(3);
- let promises = [ promise1, promise2, promise3 ];
-
- Promise.all(promises).then(function(array){
- // The array here would be [ 1, 2, 3 ];
- });
- ```
-
- If any of the `promises` given to `all` are rejected, the first promise
- that is rejected will be given as an argument to the returned promises's
- rejection handler. For example:
-
- Example:
-
- ```javascript
- let promise1 = resolve(1);
- let promise2 = reject(new Error("2"));
- let promise3 = reject(new Error("3"));
- let promises = [ promise1, promise2, promise3 ];
-
- Promise.all(promises).then(function(array){
- // Code here never runs because there are rejected promises!
- }, function(error) {
- // error.message === "2"
- });
- ```
-
- @method all
- @static
- @param {Array} entries array of promises
- @param {String} label optional string for labeling the promise.
- Useful for tooling.
- @return {Promise} promise that is fulfilled when all `promises` have been
- fulfilled, or rejected if any of them become rejected.
- @static
-*/
-function all(entries) {
- return new Enumerator(this, entries).promise;
-}
-
-/**
- `Promise.race` returns a new promise which is settled in the same way as the
- first passed promise to settle.
-
- Example:
-
- ```javascript
- let promise1 = new Promise(function(resolve, reject){
- setTimeout(function(){
- resolve('promise 1');
- }, 200);
- });
-
- let promise2 = new Promise(function(resolve, reject){
- setTimeout(function(){
- resolve('promise 2');
- }, 100);
- });
-
- Promise.race([promise1, promise2]).then(function(result){
- // result === 'promise 2' because it was resolved before promise1
- // was resolved.
- });
- ```
-
- `Promise.race` is deterministic in that only the state of the first
- settled promise matters. For example, even if other promises given to the
- `promises` array argument are resolved, but the first settled promise has
- become rejected before the other promises became fulfilled, the returned
- promise will become rejected:
-
- ```javascript
- let promise1 = new Promise(function(resolve, reject){
- setTimeout(function(){
- resolve('promise 1');
- }, 200);
- });
-
- let promise2 = new Promise(function(resolve, reject){
- setTimeout(function(){
- reject(new Error('promise 2'));
- }, 100);
- });
-
- Promise.race([promise1, promise2]).then(function(result){
- // Code here never runs
- }, function(reason){
- // reason.message === 'promise 2' because promise 2 became rejected before
- // promise 1 became fulfilled
- });
- ```
-
- An example real-world use case is implementing timeouts:
-
- ```javascript
- Promise.race([ajax('foo.json'), timeout(5000)])
- ```
-
- @method race
- @static
- @param {Array} promises array of promises to observe
- Useful for tooling.
- @return {Promise} a promise which settles in the same way as the first passed
- promise to settle.
-*/
-function race(entries) {
- /*jshint validthis:true */
- var Constructor = this;
-
- if (!isArray(entries)) {
- return new Constructor(function (_, reject) {
- return reject(new TypeError('You must pass an array to race.'));
- });
- } else {
- return new Constructor(function (resolve, reject) {
- var length = entries.length;
- for (var i = 0; i < length; i++) {
- Constructor.resolve(entries[i]).then(resolve, reject);
- }
- });
- }
-}
-
-/**
- `Promise.reject` returns a promise rejected with the passed `reason`.
- It is shorthand for the following:
-
- ```javascript
- let promise = new Promise(function(resolve, reject){
- reject(new Error('WHOOPS'));
- });
-
- promise.then(function(value){
- // Code here doesn't run because the promise is rejected!
- }, function(reason){
- // reason.message === 'WHOOPS'
- });
- ```
-
- Instead of writing the above, your code now simply becomes the following:
-
- ```javascript
- let promise = Promise.reject(new Error('WHOOPS'));
-
- promise.then(function(value){
- // Code here doesn't run because the promise is rejected!
- }, function(reason){
- // reason.message === 'WHOOPS'
- });
- ```
-
- @method reject
- @static
- @param {Any} reason value that the returned promise will be rejected with.
- Useful for tooling.
- @return {Promise} a promise rejected with the given `reason`.
-*/
-function reject(reason) {
- /*jshint validthis:true */
- var Constructor = this;
- var promise = new Constructor(noop);
- _reject(promise, reason);
- return promise;
-}
-
-function needsResolver() {
- throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
-}
-
-function needsNew() {
- throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
-}
-
-/**
- Promise objects represent the eventual result of an asynchronous operation. The
- primary way of interacting with a promise is through its `then` method, which
- registers callbacks to receive either a promise's eventual value or the reason
- why the promise cannot be fulfilled.
-
- Terminology
- -----------
-
- - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
- - `thenable` is an object or function that defines a `then` method.
- - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
- - `exception` is a value that is thrown using the throw statement.
- - `reason` is a value that indicates why a promise was rejected.
- - `settled` the final resting state of a promise, fulfilled or rejected.
-
- A promise can be in one of three states: pending, fulfilled, or rejected.
-
- Promises that are fulfilled have a fulfillment value and are in the fulfilled
- state. Promises that are rejected have a rejection reason and are in the
- rejected state. A fulfillment value is never a thenable.
-
- Promises can also be said to *resolve* a value. If this value is also a
- promise, then the original promise's settled state will match the value's
- settled state. So a promise that *resolves* a promise that rejects will
- itself reject, and a promise that *resolves* a promise that fulfills will
- itself fulfill.
-
-
- Basic Usage:
- ------------
-
- ```js
- let promise = new Promise(function(resolve, reject) {
- // on success
- resolve(value);
-
- // on failure
- reject(reason);
- });
-
- promise.then(function(value) {
- // on fulfillment
- }, function(reason) {
- // on rejection
- });
- ```
-
- Advanced Usage:
- ---------------
-
- Promises shine when abstracting away asynchronous interactions such as
- `XMLHttpRequest`s.
-
- ```js
- function getJSON(url) {
- return new Promise(function(resolve, reject){
- let xhr = new XMLHttpRequest();
-
- xhr.open('GET', url);
- xhr.onreadystatechange = handler;
- xhr.responseType = 'json';
- xhr.setRequestHeader('Accept', 'application/json');
- xhr.send();
-
- function handler() {
- if (this.readyState === this.DONE) {
- if (this.status === 200) {
- resolve(this.response);
- } else {
- reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
- }
- }
- };
- });
- }
-
- getJSON('/posts.json').then(function(json) {
- // on fulfillment
- }, function(reason) {
- // on rejection
- });
- ```
-
- Unlike callbacks, promises are great composable primitives.
-
- ```js
- Promise.all([
- getJSON('/posts'),
- getJSON('/comments')
- ]).then(function(values){
- values[0] // => postsJSON
- values[1] // => commentsJSON
-
- return values;
- });
- ```
-
- @class Promise
- @param {function} resolver
- Useful for tooling.
- @constructor
-*/
-function Promise(resolver) {
- this[PROMISE_ID] = nextId();
- this._result = this._state = undefined;
- this._subscribers = [];
-
- if (noop !== resolver) {
- typeof resolver !== 'function' && needsResolver();
- this instanceof Promise ? initializePromise(this, resolver) : needsNew();
- }
-}
-
-Promise.all = all;
-Promise.race = race;
-Promise.resolve = resolve;
-Promise.reject = reject;
-Promise._setScheduler = setScheduler;
-Promise._setAsap = setAsap;
-Promise._asap = asap;
-
-Promise.prototype = {
- constructor: Promise,
-
- /**
- The primary way of interacting with a promise is through its `then` method,
- which registers callbacks to receive either a promise's eventual value or the
- reason why the promise cannot be fulfilled.
-
- ```js
- findUser().then(function(user){
- // user is available
- }, function(reason){
- // user is unavailable, and you are given the reason why
- });
- ```
-
- Chaining
- --------
-
- The return value of `then` is itself a promise. This second, 'downstream'
- promise is resolved with the return value of the first promise's fulfillment
- or rejection handler, or rejected if the handler throws an exception.
-
- ```js
- findUser().then(function (user) {
- return user.name;
- }, function (reason) {
- return 'default name';
- }).then(function (userName) {
- // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
- // will be `'default name'`
- });
-
- findUser().then(function (user) {
- throw new Error('Found user, but still unhappy');
- }, function (reason) {
- throw new Error('`findUser` rejected and we're unhappy');
- }).then(function (value) {
- // never reached
- }, function (reason) {
- // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
- // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.
- });
- ```
- If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
-
- ```js
- findUser().then(function (user) {
- throw new PedagogicalException('Upstream error');
- }).then(function (value) {
- // never reached
- }).then(function (value) {
- // never reached
- }, function (reason) {
- // The `PedgagocialException` is propagated all the way down to here
- });
- ```
-
- Assimilation
- ------------
-
- Sometimes the value you want to propagate to a downstream promise can only be
- retrieved asynchronously. This can be achieved by returning a promise in the
- fulfillment or rejection handler. The downstream promise will then be pending
- until the returned promise is settled. This is called *assimilation*.
-
- ```js
- findUser().then(function (user) {
- return findCommentsByAuthor(user);
- }).then(function (comments) {
- // The user's comments are now available
- });
- ```
-
- If the assimliated promise rejects, then the downstream promise will also reject.
-
- ```js
- findUser().then(function (user) {
- return findCommentsByAuthor(user);
- }).then(function (comments) {
- // If `findCommentsByAuthor` fulfills, we'll have the value here
- }, function (reason) {
- // If `findCommentsByAuthor` rejects, we'll have the reason here
- });
- ```
-
- Simple Example
- --------------
-
- Synchronous Example
-
- ```javascript
- let result;
-
- try {
- result = findResult();
- // success
- } catch(reason) {
- // failure
- }
- ```
-
- Errback Example
-
- ```js
- findResult(function(result, err){
- if (err) {
- // failure
- } else {
- // success
- }
- });
- ```
-
- Promise Example;
-
- ```javascript
- findResult().then(function(result){
- // success
- }, function(reason){
- // failure
- });
- ```
-
- Advanced Example
- --------------
-
- Synchronous Example
-
- ```javascript
- let author, books;
-
- try {
- author = findAuthor();
- books = findBooksByAuthor(author);
- // success
- } catch(reason) {
- // failure
- }
- ```
-
- Errback Example
-
- ```js
-
- function foundBooks(books) {
-
- }
-
- function failure(reason) {
-
- }
-
- findAuthor(function(author, err){
- if (err) {
- failure(err);
- // failure
- } else {
- try {
- findBoooksByAuthor(author, function(books, err) {
- if (err) {
- failure(err);
- } else {
- try {
- foundBooks(books);
- } catch(reason) {
- failure(reason);
- }
- }
- });
- } catch(error) {
- failure(err);
- }
- // success
- }
- });
- ```
-
- Promise Example;
-
- ```javascript
- findAuthor().
- then(findBooksByAuthor).
- then(function(books){
- // found books
- }).catch(function(reason){
- // something went wrong
- });
- ```
-
- @method then
- @param {Function} onFulfilled
- @param {Function} onRejected
- Useful for tooling.
- @return {Promise}
- */
- then: then,
-
- /**
- `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
- as the catch block of a try/catch statement.
-
- ```js
- function findAuthor(){
- throw new Error('couldn't find that author');
- }
-
- // synchronous
- try {
- findAuthor();
- } catch(reason) {
- // something went wrong
- }
-
- // async with promises
- findAuthor().catch(function(reason){
- // something went wrong
- });
- ```
-
- @method catch
- @param {Function} onRejection
- Useful for tooling.
- @return {Promise}
- */
- 'catch': function _catch(onRejection) {
- return this.then(null, onRejection);
- }
-};
-
-function polyfill() {
- var local = undefined;
-
- if (typeof global !== 'undefined') {
- local = global;
- } else if (typeof self !== 'undefined') {
- local = self;
- } else {
- try {
- local = Function('return this')();
- } catch (e) {
- throw new Error('polyfill failed because global object is unavailable in this environment');
- }
- }
-
- var P = local.Promise;
-
- if (P) {
- var promiseToString = null;
- try {
- promiseToString = Object.prototype.toString.call(P.resolve());
- } catch (e) {
- // silently ignored
- }
-
- if (promiseToString === '[object Promise]' && !P.cast) {
- return;
- }
- }
-
- local.Promise = Promise;
-}
-
-// Strange compat..
-Promise.polyfill = polyfill;
-Promise.Promise = Promise;
-
-return Promise;
-
-})));
diff --git a/vendor/assets/javascripts/timeago.js b/vendor/assets/javascripts/timeago.js
deleted file mode 100644
index 0eb6f7967a5..00000000000
--- a/vendor/assets/javascripts/timeago.js
+++ /dev/null
@@ -1,237 +0,0 @@
-/**
- * Copyright (c) 2016 hustcc
- * License: MIT
- * Version: v2.0.2
- * https://github.com/hustcc/timeago.js
- * This is a forked from (https://gitlab.com/ClemMakesApps/timeago.js)
-**/
-/* eslint-disable */
-/* jshint expr: true */
-!function (root, factory) {
- if (typeof module === 'object' && module.exports)
- module.exports = factory(root);
- else
- root.timeago = factory(root);
-}(typeof window !== 'undefined' ? window : this,
-function () {
- var cnt = 0, // the timer counter, for timer key
- indexMapEn = 'second_minute_hour_day_week_month_year'.split('_'),
-
- // build-in locales: en & zh_CN
- locales = {
- 'en': function(number, index) {
- if (index === 0) return ['just now', 'right now'];
- var unit = indexMapEn[parseInt(index / 2)];
- if (number > 1) unit += 's';
- return [number + ' ' + unit + ' ago', 'in ' + number + ' ' + unit];
- },
- },
- // second, minute, hour, day, week, month, year(365 days)
- SEC_ARRAY = [60, 60, 24, 7, 365/7/12, 12],
- SEC_ARRAY_LEN = 6,
- ATTR_DATETIME = 'datetime';
-
- // format Date / string / timestamp to Date instance.
- function toDate(input) {
- if (input instanceof Date) return input;
- if (!isNaN(input)) return new Date(toInt(input));
- if (/^\d+$/.test(input)) return new Date(toInt(input, 10));
- input = (input || '').trim().replace(/\.\d+/, '') // remove milliseconds
- .replace(/-/, '/').replace(/-/, '/')
- .replace(/T/, ' ').replace(/Z/, ' UTC')
- .replace(/([\+\-]\d\d)\:?(\d\d)/, ' $1$2'); // -04:00 -> -0400
- return new Date(input);
- }
- // change f into int, remove Decimal. just for code compression
- function toInt(f) {
- return parseInt(f);
- }
- // format the diff second to *** time ago, with setting locale
- function formatDiff(diff, locale, defaultLocale) {
- // if locale is not exist, use defaultLocale.
- // if defaultLocale is not exist, use build-in `en`.
- // be sure of no error when locale is not exist.
- locale = locales[locale] ? locale : (locales[defaultLocale] ? defaultLocale : 'en');
- // if (! locales[locale]) locale = defaultLocale;
- var i = 0;
- agoin = diff < 0 ? 1 : 0; // timein or timeago
- diff = Math.abs(diff);
-
- for (; diff >= SEC_ARRAY[i] && i < SEC_ARRAY_LEN; i++) {
- diff /= SEC_ARRAY[i];
- }
- diff = toInt(diff);
- i *= 2;
-
- if (diff > (i === 0 ? 9 : 1)) i += 1;
- return locales[locale](diff, i)[agoin].replace('%s', diff);
- }
- // calculate the diff second between date to be formated an now date.
- function diffSec(date, nowDate) {
- nowDate = nowDate ? toDate(nowDate) : new Date();
- return (nowDate - toDate(date)) / 1000;
- }
- /**
- * nextInterval: calculate the next interval time.
- * - diff: the diff sec between now and date to be formated.
- *
- * What's the meaning?
- * diff = 61 then return 59
- * diff = 3601 (an hour + 1 second), then return 3599
- * make the interval with high performace.
- **/
- function nextInterval(diff) {
- var rst = 1, i = 0, d = Math.abs(diff);
- for (; diff >= SEC_ARRAY[i] && i < SEC_ARRAY_LEN; i++) {
- diff /= SEC_ARRAY[i];
- rst *= SEC_ARRAY[i];
- }
- // return leftSec(d, rst);
- d = d % rst;
- d = d ? rst - d : rst;
- return Math.ceil(d);
- }
- // get the datetime attribute, jQuery and DOM
- function getDateAttr(node) {
- if (node.getAttribute) return node.getAttribute(ATTR_DATETIME);
- if(node.attr) return node.attr(ATTR_DATETIME);
- }
- /**
- * timeago: the function to get `timeago` instance.
- * - nowDate: the relative date, default is new Date().
- * - defaultLocale: the default locale, default is en. if your set it, then the `locale` parameter of format is not needed of you.
- *
- * How to use it?
- * var timeagoLib = require('timeago.js');
- * var timeago = timeagoLib(); // all use default.
- * var timeago = timeagoLib('2016-09-10'); // the relative date is 2016-09-10, so the 2016-09-11 will be 1 day ago.
- * var timeago = timeagoLib(null, 'zh_CN'); // set default locale is `zh_CN`.
- * var timeago = timeagoLib('2016-09-10', 'zh_CN'); // the relative date is 2016-09-10, and locale is zh_CN, so the 2016-09-11 will be 1天前.
- **/
- function Timeago(nowDate, defaultLocale) {
- var timers = {}; // real-time render timers
- // if do not set the defaultLocale, set it with `en`
- if (! defaultLocale) defaultLocale = 'en'; // use default build-in locale
- // what the timer will do
- function doRender(node, date, locale, cnt) {
- var diff = diffSec(date, nowDate);
- node.innerHTML = formatDiff(diff, locale, defaultLocale);
- // waiting %s seconds, do the next render
- timers['k' + cnt] = setTimeout(function() {
- doRender(node, date, locale, cnt);
- }, nextInterval(diff) * 1000);
- }
- /**
- * nextInterval: calculate the next interval time.
- * - diff: the diff sec between now and date to be formated.
- *
- * What's the meaning?
- * diff = 61 then return 59
- * diff = 3601 (an hour + 1 second), then return 3599
- * make the interval with high performace.
- **/
- // this.nextInterval = function(diff) { // for dev test
- // var rst = 1, i = 0, d = Math.abs(diff);
- // for (; diff >= SEC_ARRAY[i] && i < SEC_ARRAY_LEN; i++) {
- // diff /= SEC_ARRAY[i];
- // rst *= SEC_ARRAY[i];
- // }
- // // return leftSec(d, rst);
- // d = d % rst;
- // d = d ? rst - d : rst;
- // return Math.ceil(d);
- // }; // for dev test
- /**
- * format: format the date to *** time ago, with setting or default locale
- * - date: the date / string / timestamp to be formated
- * - locale: the formated string's locale name, e.g. en / zh_CN
- *
- * How to use it?
- * var timeago = require('timeago.js')();
- * timeago.format(new Date(), 'pl'); // Date instance
- * timeago.format('2016-09-10', 'fr'); // formated date string
- * timeago.format(1473473400269); // timestamp with ms
- **/
- this.format = function(date, locale) {
- return formatDiff(diffSec(date, nowDate), locale, defaultLocale);
- };
- /**
- * render: render the DOM real-time.
- * - nodes: which nodes will be rendered.
- * - locale: the locale name used to format date.
- *
- * How to use it?
- * var timeago = new require('timeago.js')();
- * // 1. javascript selector
- * timeago.render(document.querySelectorAll('.need_to_be_rendered'));
- * // 2. use jQuery selector
- * timeago.render($('.need_to_be_rendered'), 'pl');
- *
- * Notice: please be sure the dom has attribute `datetime`.
- **/
- this.render = function(nodes, locale) {
- if (nodes.length === undefined) nodes = [nodes];
- for (var i = 0; i < nodes.length; i++) {
- doRender(nodes[i], getDateAttr(nodes[i]), locale, ++ cnt); // render item
- }
- };
- /**
- * cancel: cancel all the timers which are doing real-time render.
- *
- * How to use it?
- * var timeago = new require('timeago.js')();
- * timeago.render(document.querySelectorAll('.need_to_be_rendered'));
- * timeago.cancel(); // will stop all the timer, stop render in real time.
- **/
- this.cancel = function() {
- for (var key in timers) {
- clearTimeout(timers[key]);
- }
- timers = {};
- };
- /**
- * setLocale: set the default locale name.
- *
- * How to use it?
- * var timeago = require('timeago.js');
- * timeago = new timeago();
- * timeago.setLocale('fr');
- **/
- this.setLocale = function(locale) {
- defaultLocale = locale;
- };
- return this;
- }
- /**
- * timeago: the function to get `timeago` instance.
- * - nowDate: the relative date, default is new Date().
- * - defaultLocale: the default locale, default is en. if your set it, then the `locale` parameter of format is not needed of you.
- *
- * How to use it?
- * var timeagoLib = require('timeago.js');
- * var timeago = timeagoLib(); // all use default.
- * var timeago = timeagoLib('2016-09-10'); // the relative date is 2016-09-10, so the 2016-09-11 will be 1 day ago.
- * var timeago = timeagoLib(null, 'zh_CN'); // set default locale is `zh_CN`.
- * var timeago = timeagoLib('2016-09-10', 'zh_CN'); // the relative date is 2016-09-10, and locale is zh_CN, so the 2016-09-11 will be 1天前.
- **/
- function timeagoFactory(nowDate, defaultLocale) {
- return new Timeago(nowDate, defaultLocale);
- }
- /**
- * register: register a new language locale
- * - locale: locale name, e.g. en / zh_CN, notice the standard.
- * - localeFunc: the locale process function
- *
- * How to use it?
- * var timeagoLib = require('timeago.js');
- *
- * timeagoLib.register('the locale name', the_locale_func);
- * // or
- * timeagoLib.register('pl', require('timeago.js/locales/pl'));
- **/
- timeagoFactory.register = function(locale, localeFunc) {
- locales[locale] = localeFunc;
- };
-
- return timeagoFactory;
-});
diff --git a/vendor/assets/javascripts/vue-resource.full.js b/vendor/assets/javascripts/vue-resource.full.js
deleted file mode 100644
index d7981dbec7e..00000000000
--- a/vendor/assets/javascripts/vue-resource.full.js
+++ /dev/null
@@ -1,1318 +0,0 @@
-/*!
- * vue-resource v0.9.3
- * https://github.com/vuejs/vue-resource
- * Released under the MIT License.
- */
-
-(function (global, factory) {
- typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
- typeof define === 'function' && define.amd ? define(factory) :
- (global.VueResource = factory());
-}(this, function () { 'use strict';
-
- /**
- * Promises/A+ polyfill v1.1.4 (https://github.com/bramstein/promis)
- */
-
- var RESOLVED = 0;
- var REJECTED = 1;
- var PENDING = 2;
-
- function Promise$2(executor) {
-
- this.state = PENDING;
- this.value = undefined;
- this.deferred = [];
-
- var promise = this;
-
- try {
- executor(function (x) {
- promise.resolve(x);
- }, function (r) {
- promise.reject(r);
- });
- } catch (e) {
- promise.reject(e);
- }
- }
-
- Promise$2.reject = function (r) {
- return new Promise$2(function (resolve, reject) {
- reject(r);
- });
- };
-
- Promise$2.resolve = function (x) {
- return new Promise$2(function (resolve, reject) {
- resolve(x);
- });
- };
-
- Promise$2.all = function all(iterable) {
- return new Promise$2(function (resolve, reject) {
- var count = 0,
- result = [];
-
- if (iterable.length === 0) {
- resolve(result);
- }
-
- function resolver(i) {
- return function (x) {
- result[i] = x;
- count += 1;
-
- if (count === iterable.length) {
- resolve(result);
- }
- };
- }
-
- for (var i = 0; i < iterable.length; i += 1) {
- Promise$2.resolve(iterable[i]).then(resolver(i), reject);
- }
- });
- };
-
- Promise$2.race = function race(iterable) {
- return new Promise$2(function (resolve, reject) {
- for (var i = 0; i < iterable.length; i += 1) {
- Promise$2.resolve(iterable[i]).then(resolve, reject);
- }
- });
- };
-
- var p$1 = Promise$2.prototype;
-
- p$1.resolve = function resolve(x) {
- var promise = this;
-
- if (promise.state === PENDING) {
- if (x === promise) {
- throw new TypeError('Promise settled with itself.');
- }
-
- var called = false;
-
- try {
- var then = x && x['then'];
-
- if (x !== null && typeof x === 'object' && typeof then === 'function') {
- then.call(x, function (x) {
- if (!called) {
- promise.resolve(x);
- }
- called = true;
- }, function (r) {
- if (!called) {
- promise.reject(r);
- }
- called = true;
- });
- return;
- }
- } catch (e) {
- if (!called) {
- promise.reject(e);
- }
- return;
- }
-
- promise.state = RESOLVED;
- promise.value = x;
- promise.notify();
- }
- };
-
- p$1.reject = function reject(reason) {
- var promise = this;
-
- if (promise.state === PENDING) {
- if (reason === promise) {
- throw new TypeError('Promise settled with itself.');
- }
-
- promise.state = REJECTED;
- promise.value = reason;
- promise.notify();
- }
- };
-
- p$1.notify = function notify() {
- var promise = this;
-
- nextTick(function () {
- if (promise.state !== PENDING) {
- while (promise.deferred.length) {
- var deferred = promise.deferred.shift(),
- onResolved = deferred[0],
- onRejected = deferred[1],
- resolve = deferred[2],
- reject = deferred[3];
-
- try {
- if (promise.state === RESOLVED) {
- if (typeof onResolved === 'function') {
- resolve(onResolved.call(undefined, promise.value));
- } else {
- resolve(promise.value);
- }
- } else if (promise.state === REJECTED) {
- if (typeof onRejected === 'function') {
- resolve(onRejected.call(undefined, promise.value));
- } else {
- reject(promise.value);
- }
- }
- } catch (e) {
- reject(e);
- }
- }
- }
- });
- };
-
- p$1.then = function then(onResolved, onRejected) {
- var promise = this;
-
- return new Promise$2(function (resolve, reject) {
- promise.deferred.push([onResolved, onRejected, resolve, reject]);
- promise.notify();
- });
- };
-
- p$1.catch = function (onRejected) {
- return this.then(undefined, onRejected);
- };
-
- var PromiseObj = window.Promise || Promise$2;
-
- function Promise$1(executor, context) {
-
- if (executor instanceof PromiseObj) {
- this.promise = executor;
- } else {
- this.promise = new PromiseObj(executor.bind(context));
- }
-
- this.context = context;
- }
-
- Promise$1.all = function (iterable, context) {
- return new Promise$1(PromiseObj.all(iterable), context);
- };
-
- Promise$1.resolve = function (value, context) {
- return new Promise$1(PromiseObj.resolve(value), context);
- };
-
- Promise$1.reject = function (reason, context) {
- return new Promise$1(PromiseObj.reject(reason), context);
- };
-
- Promise$1.race = function (iterable, context) {
- return new Promise$1(PromiseObj.race(iterable), context);
- };
-
- var p = Promise$1.prototype;
-
- p.bind = function (context) {
- this.context = context;
- return this;
- };
-
- p.then = function (fulfilled, rejected) {
-
- if (fulfilled && fulfilled.bind && this.context) {
- fulfilled = fulfilled.bind(this.context);
- }
-
- if (rejected && rejected.bind && this.context) {
- rejected = rejected.bind(this.context);
- }
-
- return new Promise$1(this.promise.then(fulfilled, rejected), this.context);
- };
-
- p.catch = function (rejected) {
-
- if (rejected && rejected.bind && this.context) {
- rejected = rejected.bind(this.context);
- }
-
- return new Promise$1(this.promise.catch(rejected), this.context);
- };
-
- p.finally = function (callback) {
-
- return this.then(function (value) {
- callback.call(this);
- return value;
- }, function (reason) {
- callback.call(this);
- return PromiseObj.reject(reason);
- });
- };
-
- var debug = false;
- var util = {};
- var array = [];
- function Util (Vue) {
- util = Vue.util;
- debug = Vue.config.debug || !Vue.config.silent;
- }
-
- function warn(msg) {
- if (typeof console !== 'undefined' && debug) {
- console.warn('[VueResource warn]: ' + msg);
- }
- }
-
- function error(msg) {
- if (typeof console !== 'undefined') {
- console.error(msg);
- }
- }
-
- function nextTick(cb, ctx) {
- return util.nextTick(cb, ctx);
- }
-
- function trim(str) {
- return str.replace(/^\s*|\s*$/g, '');
- }
-
- var isArray = Array.isArray;
-
- function isString(val) {
- return typeof val === 'string';
- }
-
- function isBoolean(val) {
- return val === true || val === false;
- }
-
- function isFunction(val) {
- return typeof val === 'function';
- }
-
- function isObject(obj) {
- return obj !== null && typeof obj === 'object';
- }
-
- function isPlainObject(obj) {
- return isObject(obj) && Object.getPrototypeOf(obj) == Object.prototype;
- }
-
- function isFormData(obj) {
- return typeof FormData !== 'undefined' && obj instanceof FormData;
- }
-
- function when(value, fulfilled, rejected) {
-
- var promise = Promise$1.resolve(value);
-
- if (arguments.length < 2) {
- return promise;
- }
-
- return promise.then(fulfilled, rejected);
- }
-
- function options(fn, obj, opts) {
-
- opts = opts || {};
-
- if (isFunction(opts)) {
- opts = opts.call(obj);
- }
-
- return merge(fn.bind({ $vm: obj, $options: opts }), fn, { $options: opts });
- }
-
- function each(obj, iterator) {
-
- var i, key;
-
- if (typeof obj.length == 'number') {
- for (i = 0; i < obj.length; i++) {
- iterator.call(obj[i], obj[i], i);
- }
- } else if (isObject(obj)) {
- for (key in obj) {
- if (obj.hasOwnProperty(key)) {
- iterator.call(obj[key], obj[key], key);
- }
- }
- }
-
- return obj;
- }
-
- var assign = Object.assign || _assign;
-
- function merge(target) {
-
- var args = array.slice.call(arguments, 1);
-
- args.forEach(function (source) {
- _merge(target, source, true);
- });
-
- return target;
- }
-
- function defaults(target) {
-
- var args = array.slice.call(arguments, 1);
-
- args.forEach(function (source) {
-
- for (var key in source) {
- if (target[key] === undefined) {
- target[key] = source[key];
- }
- }
- });
-
- return target;
- }
-
- function _assign(target) {
-
- var args = array.slice.call(arguments, 1);
-
- args.forEach(function (source) {
- _merge(target, source);
- });
-
- return target;
- }
-
- function _merge(target, source, deep) {
- for (var key in source) {
- if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
- if (isPlainObject(source[key]) && !isPlainObject(target[key])) {
- target[key] = {};
- }
- if (isArray(source[key]) && !isArray(target[key])) {
- target[key] = [];
- }
- _merge(target[key], source[key], deep);
- } else if (source[key] !== undefined) {
- target[key] = source[key];
- }
- }
- }
-
- function root (options, next) {
-
- var url = next(options);
-
- if (isString(options.root) && !url.match(/^(https?:)?\//)) {
- url = options.root + '/' + url;
- }
-
- return url;
- }
-
- function query (options, next) {
-
- var urlParams = Object.keys(Url.options.params),
- query = {},
- url = next(options);
-
- each(options.params, function (value, key) {
- if (urlParams.indexOf(key) === -1) {
- query[key] = value;
- }
- });
-
- query = Url.params(query);
-
- if (query) {
- url += (url.indexOf('?') == -1 ? '?' : '&') + query;
- }
-
- return url;
- }
-
- /**
- * URL Template v2.0.6 (https://github.com/bramstein/url-template)
- */
-
- function expand(url, params, variables) {
-
- var tmpl = parse(url),
- expanded = tmpl.expand(params);
-
- if (variables) {
- variables.push.apply(variables, tmpl.vars);
- }
-
- return expanded;
- }
-
- function parse(template) {
-
- var operators = ['+', '#', '.', '/', ';', '?', '&'],
- variables = [];
-
- return {
- vars: variables,
- expand: function (context) {
- return template.replace(/\{([^\{\}]+)\}|([^\{\}]+)/g, function (_, expression, literal) {
- if (expression) {
-
- var operator = null,
- values = [];
-
- if (operators.indexOf(expression.charAt(0)) !== -1) {
- operator = expression.charAt(0);
- expression = expression.substr(1);
- }
-
- expression.split(/,/g).forEach(function (variable) {
- var tmp = /([^:\*]*)(?::(\d+)|(\*))?/.exec(variable);
- values.push.apply(values, getValues(context, operator, tmp[1], tmp[2] || tmp[3]));
- variables.push(tmp[1]);
- });
-
- if (operator && operator !== '+') {
-
- var separator = ',';
-
- if (operator === '?') {
- separator = '&';
- } else if (operator !== '#') {
- separator = operator;
- }
-
- return (values.length !== 0 ? operator : '') + values.join(separator);
- } else {
- return values.join(',');
- }
- } else {
- return encodeReserved(literal);
- }
- });
- }
- };
- }
-
- function getValues(context, operator, key, modifier) {
-
- var value = context[key],
- result = [];
-
- if (isDefined(value) && value !== '') {
- if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
- value = value.toString();
-
- if (modifier && modifier !== '*') {
- value = value.substring(0, parseInt(modifier, 10));
- }
-
- result.push(encodeValue(operator, value, isKeyOperator(operator) ? key : null));
- } else {
- if (modifier === '*') {
- if (Array.isArray(value)) {
- value.filter(isDefined).forEach(function (value) {
- result.push(encodeValue(operator, value, isKeyOperator(operator) ? key : null));
- });
- } else {
- Object.keys(value).forEach(function (k) {
- if (isDefined(value[k])) {
- result.push(encodeValue(operator, value[k], k));
- }
- });
- }
- } else {
- var tmp = [];
-
- if (Array.isArray(value)) {
- value.filter(isDefined).forEach(function (value) {
- tmp.push(encodeValue(operator, value));
- });
- } else {
- Object.keys(value).forEach(function (k) {
- if (isDefined(value[k])) {
- tmp.push(encodeURIComponent(k));
- tmp.push(encodeValue(operator, value[k].toString()));
- }
- });
- }
-
- if (isKeyOperator(operator)) {
- result.push(encodeURIComponent(key) + '=' + tmp.join(','));
- } else if (tmp.length !== 0) {
- result.push(tmp.join(','));
- }
- }
- }
- } else {
- if (operator === ';') {
- result.push(encodeURIComponent(key));
- } else if (value === '' && (operator === '&' || operator === '?')) {
- result.push(encodeURIComponent(key) + '=');
- } else if (value === '') {
- result.push('');
- }
- }
-
- return result;
- }
-
- function isDefined(value) {
- return value !== undefined && value !== null;
- }
-
- function isKeyOperator(operator) {
- return operator === ';' || operator === '&' || operator === '?';
- }
-
- function encodeValue(operator, value, key) {
-
- value = operator === '+' || operator === '#' ? encodeReserved(value) : encodeURIComponent(value);
-
- if (key) {
- return encodeURIComponent(key) + '=' + value;
- } else {
- return value;
- }
- }
-
- function encodeReserved(str) {
- return str.split(/(%[0-9A-Fa-f]{2})/g).map(function (part) {
- if (!/%[0-9A-Fa-f]/.test(part)) {
- part = encodeURI(part);
- }
- return part;
- }).join('');
- }
-
- function template (options) {
-
- var variables = [],
- url = expand(options.url, options.params, variables);
-
- variables.forEach(function (key) {
- delete options.params[key];
- });
-
- return url;
- }
-
- /**
- * Service for URL templating.
- */
-
- var ie = document.documentMode;
- var el = document.createElement('a');
-
- function Url(url, params) {
-
- var self = this || {},
- options = url,
- transform;
-
- if (isString(url)) {
- options = { url: url, params: params };
- }
-
- options = merge({}, Url.options, self.$options, options);
-
- Url.transforms.forEach(function (handler) {
- transform = factory(handler, transform, self.$vm);
- });
-
- return transform(options);
- }
-
- /**
- * Url options.
- */
-
- Url.options = {
- url: '',
- root: null,
- params: {}
- };
-
- /**
- * Url transforms.
- */
-
- Url.transforms = [template, query, root];
-
- /**
- * Encodes a Url parameter string.
- *
- * @param {Object} obj
- */
-
- Url.params = function (obj) {
-
- var params = [],
- escape = encodeURIComponent;
-
- params.add = function (key, value) {
-
- if (isFunction(value)) {
- value = value();
- }
-
- if (value === null) {
- value = '';
- }
-
- this.push(escape(key) + '=' + escape(value));
- };
-
- serialize(params, obj);
-
- return params.join('&').replace(/%20/g, '+');
- };
-
- /**
- * Parse a URL and return its components.
- *
- * @param {String} url
- */
-
- Url.parse = function (url) {
-
- if (ie) {
- el.href = url;
- url = el.href;
- }
-
- el.href = url;
-
- return {
- href: el.href,
- protocol: el.protocol ? el.protocol.replace(/:$/, '') : '',
- port: el.port,
- host: el.host,
- hostname: el.hostname,
- pathname: el.pathname.charAt(0) === '/' ? el.pathname : '/' + el.pathname,
- search: el.search ? el.search.replace(/^\?/, '') : '',
- hash: el.hash ? el.hash.replace(/^#/, '') : ''
- };
- };
-
- function factory(handler, next, vm) {
- return function (options) {
- return handler.call(vm, options, next);
- };
- }
-
- function serialize(params, obj, scope) {
-
- var array = isArray(obj),
- plain = isPlainObject(obj),
- hash;
-
- each(obj, function (value, key) {
-
- hash = isObject(value) || isArray(value);
-
- if (scope) {
- key = scope + '[' + (plain || hash ? key : '') + ']';
- }
-
- if (!scope && array) {
- params.add(value.name, value.value);
- } else if (hash) {
- serialize(params, value, key);
- } else {
- params.add(key, value);
- }
- });
- }
-
- function xdrClient (request) {
- return new Promise$1(function (resolve) {
-
- var xdr = new XDomainRequest(),
- handler = function (event) {
-
- var response = request.respondWith(xdr.responseText, {
- status: xdr.status,
- statusText: xdr.statusText
- });
-
- resolve(response);
- };
-
- request.abort = function () {
- return xdr.abort();
- };
-
- xdr.open(request.method, request.getUrl(), true);
- xdr.timeout = 0;
- xdr.onload = handler;
- xdr.onerror = handler;
- xdr.ontimeout = function () {};
- xdr.onprogress = function () {};
- xdr.send(request.getBody());
- });
- }
-
- var ORIGIN_URL = Url.parse(location.href);
- var SUPPORTS_CORS = 'withCredentials' in new XMLHttpRequest();
-
- function cors (request, next) {
-
- if (!isBoolean(request.crossOrigin) && crossOrigin(request)) {
- request.crossOrigin = true;
- }
-
- if (request.crossOrigin) {
-
- if (!SUPPORTS_CORS) {
- request.client = xdrClient;
- }
-
- delete request.emulateHTTP;
- }
-
- next();
- }
-
- function crossOrigin(request) {
-
- var requestUrl = Url.parse(Url(request));
-
- return requestUrl.protocol !== ORIGIN_URL.protocol || requestUrl.host !== ORIGIN_URL.host;
- }
-
- function body (request, next) {
-
- if (request.emulateJSON && isPlainObject(request.body)) {
- request.body = Url.params(request.body);
- request.headers['Content-Type'] = 'application/x-www-form-urlencoded';
- }
-
- if (isFormData(request.body)) {
- delete request.headers['Content-Type'];
- }
-
- if (isPlainObject(request.body)) {
- request.body = JSON.stringify(request.body);
- }
-
- next(function (response) {
-
- var contentType = response.headers['Content-Type'];
-
- if (isString(contentType) && contentType.indexOf('application/json') === 0) {
-
- try {
- response.data = response.json();
- } catch (e) {
- response.data = null;
- }
- } else {
- response.data = response.text();
- }
- });
- }
-
- function jsonpClient (request) {
- return new Promise$1(function (resolve) {
-
- var name = request.jsonp || 'callback',
- callback = '_jsonp' + Math.random().toString(36).substr(2),
- body = null,
- handler,
- script;
-
- handler = function (event) {
-
- var status = 0;
-
- if (event.type === 'load' && body !== null) {
- status = 200;
- } else if (event.type === 'error') {
- status = 404;
- }
-
- resolve(request.respondWith(body, { status: status }));
-
- delete window[callback];
- document.body.removeChild(script);
- };
-
- request.params[name] = callback;
-
- window[callback] = function (result) {
- body = JSON.stringify(result);
- };
-
- script = document.createElement('script');
- script.src = request.getUrl();
- script.type = 'text/javascript';
- script.async = true;
- script.onload = handler;
- script.onerror = handler;
-
- document.body.appendChild(script);
- });
- }
-
- function jsonp (request, next) {
-
- if (request.method == 'JSONP') {
- request.client = jsonpClient;
- }
-
- next(function (response) {
-
- if (request.method == 'JSONP') {
- response.data = response.json();
- }
- });
- }
-
- function before (request, next) {
-
- if (isFunction(request.before)) {
- request.before.call(this, request);
- }
-
- next();
- }
-
- /**
- * HTTP method override Interceptor.
- */
-
- function method (request, next) {
-
- if (request.emulateHTTP && /^(PUT|PATCH|DELETE)$/i.test(request.method)) {
- request.headers['X-HTTP-Method-Override'] = request.method;
- request.method = 'POST';
- }
-
- next();
- }
-
- function header (request, next) {
-
- request.method = request.method.toUpperCase();
- request.headers = assign({}, Http.headers.common, !request.crossOrigin ? Http.headers.custom : {}, Http.headers[request.method.toLowerCase()], request.headers);
-
- next();
- }
-
- /**
- * Timeout Interceptor.
- */
-
- function timeout (request, next) {
-
- var timeout;
-
- if (request.timeout) {
- timeout = setTimeout(function () {
- request.abort();
- }, request.timeout);
- }
-
- next(function (response) {
-
- clearTimeout(timeout);
- });
- }
-
- function xhrClient (request) {
- return new Promise$1(function (resolve) {
-
- var xhr = new XMLHttpRequest(),
- handler = function (event) {
-
- var response = request.respondWith('response' in xhr ? xhr.response : xhr.responseText, {
- status: xhr.status === 1223 ? 204 : xhr.status, // IE9 status bug
- statusText: xhr.status === 1223 ? 'No Content' : trim(xhr.statusText),
- headers: parseHeaders(xhr.getAllResponseHeaders())
- });
-
- resolve(response);
- };
-
- request.abort = function () {
- return xhr.abort();
- };
-
- xhr.open(request.method, request.getUrl(), true);
- xhr.timeout = 0;
- xhr.onload = handler;
- xhr.onerror = handler;
-
- if (request.progress) {
- if (request.method === 'GET') {
- xhr.addEventListener('progress', request.progress);
- } else if (/^(POST|PUT)$/i.test(request.method)) {
- xhr.upload.addEventListener('progress', request.progress);
- }
- }
-
- if (request.credentials === true) {
- xhr.withCredentials = true;
- }
-
- each(request.headers || {}, function (value, header) {
- xhr.setRequestHeader(header, value);
- });
-
- xhr.send(request.getBody());
- });
- }
-
- function parseHeaders(str) {
-
- var headers = {},
- value,
- name,
- i;
-
- each(trim(str).split('\n'), function (row) {
-
- i = row.indexOf(':');
- name = trim(row.slice(0, i));
- value = trim(row.slice(i + 1));
-
- if (headers[name]) {
-
- if (isArray(headers[name])) {
- headers[name].push(value);
- } else {
- headers[name] = [headers[name], value];
- }
- } else {
-
- headers[name] = value;
- }
- });
-
- return headers;
- }
-
- function Client (context) {
-
- var reqHandlers = [sendRequest],
- resHandlers = [],
- handler;
-
- if (!isObject(context)) {
- context = null;
- }
-
- function Client(request) {
- return new Promise$1(function (resolve) {
-
- function exec() {
-
- handler = reqHandlers.pop();
-
- if (isFunction(handler)) {
- handler.call(context, request, next);
- } else {
- warn('Invalid interceptor of type ' + typeof handler + ', must be a function');
- next();
- }
- }
-
- function next(response) {
-
- if (isFunction(response)) {
-
- resHandlers.unshift(response);
- } else if (isObject(response)) {
-
- resHandlers.forEach(function (handler) {
- response = when(response, function (response) {
- return handler.call(context, response) || response;
- });
- });
-
- when(response, resolve);
-
- return;
- }
-
- exec();
- }
-
- exec();
- }, context);
- }
-
- Client.use = function (handler) {
- reqHandlers.push(handler);
- };
-
- return Client;
- }
-
- function sendRequest(request, resolve) {
-
- var client = request.client || xhrClient;
-
- resolve(client(request));
- }
-
- var classCallCheck = function (instance, Constructor) {
- if (!(instance instanceof Constructor)) {
- throw new TypeError("Cannot call a class as a function");
- }
- };
-
- /**
- * HTTP Response.
- */
-
- var Response = function () {
- function Response(body, _ref) {
- var url = _ref.url;
- var headers = _ref.headers;
- var status = _ref.status;
- var statusText = _ref.statusText;
- classCallCheck(this, Response);
-
-
- this.url = url;
- this.body = body;
- this.headers = headers || {};
- this.status = status || 0;
- this.statusText = statusText || '';
- this.ok = status >= 200 && status < 300;
- }
-
- Response.prototype.text = function text() {
- return this.body;
- };
-
- Response.prototype.blob = function blob() {
- return new Blob([this.body]);
- };
-
- Response.prototype.json = function json() {
- return JSON.parse(this.body);
- };
-
- return Response;
- }();
-
- var Request = function () {
- function Request(options) {
- classCallCheck(this, Request);
-
-
- this.method = 'GET';
- this.body = null;
- this.params = {};
- this.headers = {};
-
- assign(this, options);
- }
-
- Request.prototype.getUrl = function getUrl() {
- return Url(this);
- };
-
- Request.prototype.getBody = function getBody() {
- return this.body;
- };
-
- Request.prototype.respondWith = function respondWith(body, options) {
- return new Response(body, assign(options || {}, { url: this.getUrl() }));
- };
-
- return Request;
- }();
-
- /**
- * Service for sending network requests.
- */
-
- var CUSTOM_HEADERS = { 'X-Requested-With': 'XMLHttpRequest' };
- var COMMON_HEADERS = { 'Accept': 'application/json, text/plain, */*' };
- var JSON_CONTENT_TYPE = { 'Content-Type': 'application/json;charset=utf-8' };
-
- function Http(options) {
-
- var self = this || {},
- client = Client(self.$vm);
-
- defaults(options || {}, self.$options, Http.options);
-
- Http.interceptors.forEach(function (handler) {
- client.use(handler);
- });
-
- return client(new Request(options)).then(function (response) {
-
- return response.ok ? response : Promise$1.reject(response);
- }, function (response) {
-
- if (response instanceof Error) {
- error(response);
- }
-
- return Promise$1.reject(response);
- });
- }
-
- Http.options = {};
-
- Http.headers = {
- put: JSON_CONTENT_TYPE,
- post: JSON_CONTENT_TYPE,
- patch: JSON_CONTENT_TYPE,
- delete: JSON_CONTENT_TYPE,
- custom: CUSTOM_HEADERS,
- common: COMMON_HEADERS
- };
-
- Http.interceptors = [before, timeout, method, body, jsonp, header, cors];
-
- ['get', 'delete', 'head', 'jsonp'].forEach(function (method) {
-
- Http[method] = function (url, options) {
- return this(assign(options || {}, { url: url, method: method }));
- };
- });
-
- ['post', 'put', 'patch'].forEach(function (method) {
-
- Http[method] = function (url, body, options) {
- return this(assign(options || {}, { url: url, method: method, body: body }));
- };
- });
-
- function Resource(url, params, actions, options) {
-
- var self = this || {},
- resource = {};
-
- actions = assign({}, Resource.actions, actions);
-
- each(actions, function (action, name) {
-
- action = merge({ url: url, params: params || {} }, options, action);
-
- resource[name] = function () {
- return (self.$http || Http)(opts(action, arguments));
- };
- });
-
- return resource;
- }
-
- function opts(action, args) {
-
- var options = assign({}, action),
- params = {},
- body;
-
- switch (args.length) {
-
- case 2:
-
- params = args[0];
- body = args[1];
-
- break;
-
- case 1:
-
- if (/^(POST|PUT|PATCH)$/i.test(options.method)) {
- body = args[0];
- } else {
- params = args[0];
- }
-
- break;
-
- case 0:
-
- break;
-
- default:
-
- throw 'Expected up to 4 arguments [params, body], got ' + args.length + ' arguments';
- }
-
- options.body = body;
- options.params = assign({}, options.params, params);
-
- return options;
- }
-
- Resource.actions = {
-
- get: { method: 'GET' },
- save: { method: 'POST' },
- query: { method: 'GET' },
- update: { method: 'PUT' },
- remove: { method: 'DELETE' },
- delete: { method: 'DELETE' }
-
- };
-
- function plugin(Vue) {
-
- if (plugin.installed) {
- return;
- }
-
- Util(Vue);
-
- Vue.url = Url;
- Vue.http = Http;
- Vue.resource = Resource;
- Vue.Promise = Promise$1;
-
- Object.defineProperties(Vue.prototype, {
-
- $url: {
- get: function () {
- return options(Vue.url, this, this.$options.url);
- }
- },
-
- $http: {
- get: function () {
- return options(Vue.http, this, this.$options.http);
- }
- },
-
- $resource: {
- get: function () {
- return Vue.resource.bind(this);
- }
- },
-
- $promise: {
- get: function () {
- var _this = this;
-
- return function (executor) {
- return new Vue.Promise(executor, _this);
- };
- }
- }
-
- });
- }
-
- if (typeof window !== 'undefined' && window.Vue) {
- window.Vue.use(plugin);
- }
-
- return plugin;
-
-})); \ No newline at end of file
diff --git a/vendor/assets/javascripts/vue-resource.js.erb b/vendor/assets/javascripts/vue-resource.js.erb
deleted file mode 100644
index 8001775ce98..00000000000
--- a/vendor/assets/javascripts/vue-resource.js.erb
+++ /dev/null
@@ -1,2 +0,0 @@
-<% type = Rails.env.development? ? 'full' : 'min' %>
-<%= File.read(Rails.root.join("vendor/assets/javascripts/vue-resource.#{type}.js")) %>
diff --git a/vendor/assets/javascripts/vue-resource.min.js b/vendor/assets/javascripts/vue-resource.min.js
deleted file mode 100644
index 6bff73a2a67..00000000000
--- a/vendor/assets/javascripts/vue-resource.min.js
+++ /dev/null
@@ -1,7 +0,0 @@
-/*!
- * vue-resource v0.9.3
- * https://github.com/vuejs/vue-resource
- * Released under the MIT License.
- */
-
-!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):t.VueResource=n()}(this,function(){"use strict";function t(t){this.state=Z,this.value=void 0,this.deferred=[];var n=this;try{t(function(t){n.resolve(t)},function(t){n.reject(t)})}catch(e){n.reject(e)}}function n(t,n){t instanceof nt?this.promise=t:this.promise=new nt(t.bind(n)),this.context=n}function e(t){rt=t.util,ot=t.config.debug||!t.config.silent}function o(t){"undefined"!=typeof console&&ot&&console.warn("[VueResource warn]: "+t)}function r(t){"undefined"!=typeof console&&console.error(t)}function i(t,n){return rt.nextTick(t,n)}function u(t){return t.replace(/^\s*|\s*$/g,"")}function s(t){return"string"==typeof t}function c(t){return t===!0||t===!1}function a(t){return"function"==typeof t}function f(t){return null!==t&&"object"==typeof t}function h(t){return f(t)&&Object.getPrototypeOf(t)==Object.prototype}function p(t){return"undefined"!=typeof FormData&&t instanceof FormData}function l(t,e,o){var r=n.resolve(t);return arguments.length<2?r:r.then(e,o)}function d(t,n,e){return e=e||{},a(e)&&(e=e.call(n)),v(t.bind({$vm:n,$options:e}),t,{$options:e})}function m(t,n){var e,o;if("number"==typeof t.length)for(e=0;e<t.length;e++)n.call(t[e],t[e],e);else if(f(t))for(o in t)t.hasOwnProperty(o)&&n.call(t[o],t[o],o);return t}function v(t){var n=it.slice.call(arguments,1);return n.forEach(function(n){g(t,n,!0)}),t}function y(t){var n=it.slice.call(arguments,1);return n.forEach(function(n){for(var e in n)void 0===t[e]&&(t[e]=n[e])}),t}function b(t){var n=it.slice.call(arguments,1);return n.forEach(function(n){g(t,n)}),t}function g(t,n,e){for(var o in n)e&&(h(n[o])||ut(n[o]))?(h(n[o])&&!h(t[o])&&(t[o]={}),ut(n[o])&&!ut(t[o])&&(t[o]=[]),g(t[o],n[o],e)):void 0!==n[o]&&(t[o]=n[o])}function w(t,n){var e=n(t);return s(t.root)&&!e.match(/^(https?:)?\//)&&(e=t.root+"/"+e),e}function T(t,n){var e=Object.keys(R.options.params),o={},r=n(t);return m(t.params,function(t,n){e.indexOf(n)===-1&&(o[n]=t)}),o=R.params(o),o&&(r+=(r.indexOf("?")==-1?"?":"&")+o),r}function j(t,n,e){var o=E(t),r=o.expand(n);return e&&e.push.apply(e,o.vars),r}function E(t){var n=["+","#",".","/",";","?","&"],e=[];return{vars:e,expand:function(o){return t.replace(/\{([^\{\}]+)\}|([^\{\}]+)/g,function(t,r,i){if(r){var u=null,s=[];if(n.indexOf(r.charAt(0))!==-1&&(u=r.charAt(0),r=r.substr(1)),r.split(/,/g).forEach(function(t){var n=/([^:\*]*)(?::(\d+)|(\*))?/.exec(t);s.push.apply(s,x(o,u,n[1],n[2]||n[3])),e.push(n[1])}),u&&"+"!==u){var c=",";return"?"===u?c="&":"#"!==u&&(c=u),(0!==s.length?u:"")+s.join(c)}return s.join(",")}return $(i)})}}}function x(t,n,e,o){var r=t[e],i=[];if(O(r)&&""!==r)if("string"==typeof r||"number"==typeof r||"boolean"==typeof r)r=r.toString(),o&&"*"!==o&&(r=r.substring(0,parseInt(o,10))),i.push(C(n,r,P(n)?e:null));else if("*"===o)Array.isArray(r)?r.filter(O).forEach(function(t){i.push(C(n,t,P(n)?e:null))}):Object.keys(r).forEach(function(t){O(r[t])&&i.push(C(n,r[t],t))});else{var u=[];Array.isArray(r)?r.filter(O).forEach(function(t){u.push(C(n,t))}):Object.keys(r).forEach(function(t){O(r[t])&&(u.push(encodeURIComponent(t)),u.push(C(n,r[t].toString())))}),P(n)?i.push(encodeURIComponent(e)+"="+u.join(",")):0!==u.length&&i.push(u.join(","))}else";"===n?i.push(encodeURIComponent(e)):""!==r||"&"!==n&&"?"!==n?""===r&&i.push(""):i.push(encodeURIComponent(e)+"=");return i}function O(t){return void 0!==t&&null!==t}function P(t){return";"===t||"&"===t||"?"===t}function C(t,n,e){return n="+"===t||"#"===t?$(n):encodeURIComponent(n),e?encodeURIComponent(e)+"="+n:n}function $(t){return t.split(/(%[0-9A-Fa-f]{2})/g).map(function(t){return/%[0-9A-Fa-f]/.test(t)||(t=encodeURI(t)),t}).join("")}function U(t){var n=[],e=j(t.url,t.params,n);return n.forEach(function(n){delete t.params[n]}),e}function R(t,n){var e,o=this||{},r=t;return s(t)&&(r={url:t,params:n}),r=v({},R.options,o.$options,r),R.transforms.forEach(function(t){e=A(t,e,o.$vm)}),e(r)}function A(t,n,e){return function(o){return t.call(e,o,n)}}function S(t,n,e){var o,r=ut(n),i=h(n);m(n,function(n,u){o=f(n)||ut(n),e&&(u=e+"["+(i||o?u:"")+"]"),!e&&r?t.add(n.name,n.value):o?S(t,n,u):t.add(u,n)})}function k(t){return new n(function(n){var e=new XDomainRequest,o=function(o){var r=t.respondWith(e.responseText,{status:e.status,statusText:e.statusText});n(r)};t.abort=function(){return e.abort()},e.open(t.method,t.getUrl(),!0),e.timeout=0,e.onload=o,e.onerror=o,e.ontimeout=function(){},e.onprogress=function(){},e.send(t.getBody())})}function H(t,n){!c(t.crossOrigin)&&I(t)&&(t.crossOrigin=!0),t.crossOrigin&&(ht||(t.client=k),delete t.emulateHTTP),n()}function I(t){var n=R.parse(R(t));return n.protocol!==ft.protocol||n.host!==ft.host}function L(t,n){t.emulateJSON&&h(t.body)&&(t.body=R.params(t.body),t.headers["Content-Type"]="application/x-www-form-urlencoded"),p(t.body)&&delete t.headers["Content-Type"],h(t.body)&&(t.body=JSON.stringify(t.body)),n(function(t){var n=t.headers["Content-Type"];if(s(n)&&0===n.indexOf("application/json"))try{t.data=t.json()}catch(e){t.data=null}else t.data=t.text()})}function q(t){return new n(function(n){var e,o,r=t.jsonp||"callback",i="_jsonp"+Math.random().toString(36).substr(2),u=null;e=function(e){var r=0;"load"===e.type&&null!==u?r=200:"error"===e.type&&(r=404),n(t.respondWith(u,{status:r})),delete window[i],document.body.removeChild(o)},t.params[r]=i,window[i]=function(t){u=JSON.stringify(t)},o=document.createElement("script"),o.src=t.getUrl(),o.type="text/javascript",o.async=!0,o.onload=e,o.onerror=e,document.body.appendChild(o)})}function N(t,n){"JSONP"==t.method&&(t.client=q),n(function(n){"JSONP"==t.method&&(n.data=n.json())})}function D(t,n){a(t.before)&&t.before.call(this,t),n()}function J(t,n){t.emulateHTTP&&/^(PUT|PATCH|DELETE)$/i.test(t.method)&&(t.headers["X-HTTP-Method-Override"]=t.method,t.method="POST"),n()}function M(t,n){t.method=t.method.toUpperCase(),t.headers=st({},V.headers.common,t.crossOrigin?{}:V.headers.custom,V.headers[t.method.toLowerCase()],t.headers),n()}function X(t,n){var e;t.timeout&&(e=setTimeout(function(){t.abort()},t.timeout)),n(function(t){clearTimeout(e)})}function W(t){return new n(function(n){var e=new XMLHttpRequest,o=function(o){var r=t.respondWith("response"in e?e.response:e.responseText,{status:1223===e.status?204:e.status,statusText:1223===e.status?"No Content":u(e.statusText),headers:B(e.getAllResponseHeaders())});n(r)};t.abort=function(){return e.abort()},e.open(t.method,t.getUrl(),!0),e.timeout=0,e.onload=o,e.onerror=o,t.progress&&("GET"===t.method?e.addEventListener("progress",t.progress):/^(POST|PUT)$/i.test(t.method)&&e.upload.addEventListener("progress",t.progress)),t.credentials===!0&&(e.withCredentials=!0),m(t.headers||{},function(t,n){e.setRequestHeader(n,t)}),e.send(t.getBody())})}function B(t){var n,e,o,r={};return m(u(t).split("\n"),function(t){o=t.indexOf(":"),e=u(t.slice(0,o)),n=u(t.slice(o+1)),r[e]?ut(r[e])?r[e].push(n):r[e]=[r[e],n]:r[e]=n}),r}function F(t){function e(e){return new n(function(n){function s(){r=i.pop(),a(r)?r.call(t,e,c):(o("Invalid interceptor of type "+typeof r+", must be a function"),c())}function c(e){if(a(e))u.unshift(e);else if(f(e))return u.forEach(function(n){e=l(e,function(e){return n.call(t,e)||e})}),void l(e,n);s()}s()},t)}var r,i=[G],u=[];return f(t)||(t=null),e.use=function(t){i.push(t)},e}function G(t,n){var e=t.client||W;n(e(t))}function V(t){var e=this||{},o=F(e.$vm);return y(t||{},e.$options,V.options),V.interceptors.forEach(function(t){o.use(t)}),o(new dt(t)).then(function(t){return t.ok?t:n.reject(t)},function(t){return t instanceof Error&&r(t),n.reject(t)})}function _(t,n,e,o){var r=this||{},i={};return e=st({},_.actions,e),m(e,function(e,u){e=v({url:t,params:n||{}},o,e),i[u]=function(){return(r.$http||V)(z(e,arguments))}}),i}function z(t,n){var e,o=st({},t),r={};switch(n.length){case 2:r=n[0],e=n[1];break;case 1:/^(POST|PUT|PATCH)$/i.test(o.method)?e=n[0]:r=n[0];break;case 0:break;default:throw"Expected up to 4 arguments [params, body], got "+n.length+" arguments"}return o.body=e,o.params=st({},o.params,r),o}function K(t){K.installed||(e(t),t.url=R,t.http=V,t.resource=_,t.Promise=n,Object.defineProperties(t.prototype,{$url:{get:function(){return d(t.url,this,this.$options.url)}},$http:{get:function(){return d(t.http,this,this.$options.http)}},$resource:{get:function(){return t.resource.bind(this)}},$promise:{get:function(){var n=this;return function(e){return new t.Promise(e,n)}}}}))}var Q=0,Y=1,Z=2;t.reject=function(n){return new t(function(t,e){e(n)})},t.resolve=function(n){return new t(function(t,e){t(n)})},t.all=function(n){return new t(function(e,o){function r(t){return function(o){u[t]=o,i+=1,i===n.length&&e(u)}}var i=0,u=[];0===n.length&&e(u);for(var s=0;s<n.length;s+=1)t.resolve(n[s]).then(r(s),o)})},t.race=function(n){return new t(function(e,o){for(var r=0;r<n.length;r+=1)t.resolve(n[r]).then(e,o)})};var tt=t.prototype;tt.resolve=function(t){var n=this;if(n.state===Z){if(t===n)throw new TypeError("Promise settled with itself.");var e=!1;try{var o=t&&t.then;if(null!==t&&"object"==typeof t&&"function"==typeof o)return void o.call(t,function(t){e||n.resolve(t),e=!0},function(t){e||n.reject(t),e=!0})}catch(r){return void(e||n.reject(r))}n.state=Q,n.value=t,n.notify()}},tt.reject=function(t){var n=this;if(n.state===Z){if(t===n)throw new TypeError("Promise settled with itself.");n.state=Y,n.value=t,n.notify()}},tt.notify=function(){var t=this;i(function(){if(t.state!==Z)for(;t.deferred.length;){var n=t.deferred.shift(),e=n[0],o=n[1],r=n[2],i=n[3];try{t.state===Q?r("function"==typeof e?e.call(void 0,t.value):t.value):t.state===Y&&("function"==typeof o?r(o.call(void 0,t.value)):i(t.value))}catch(u){i(u)}}})},tt.then=function(n,e){var o=this;return new t(function(t,r){o.deferred.push([n,e,t,r]),o.notify()})},tt["catch"]=function(t){return this.then(void 0,t)};var nt=window.Promise||t;n.all=function(t,e){return new n(nt.all(t),e)},n.resolve=function(t,e){return new n(nt.resolve(t),e)},n.reject=function(t,e){return new n(nt.reject(t),e)},n.race=function(t,e){return new n(nt.race(t),e)};var et=n.prototype;et.bind=function(t){return this.context=t,this},et.then=function(t,e){return t&&t.bind&&this.context&&(t=t.bind(this.context)),e&&e.bind&&this.context&&(e=e.bind(this.context)),new n(this.promise.then(t,e),this.context)},et["catch"]=function(t){return t&&t.bind&&this.context&&(t=t.bind(this.context)),new n(this.promise["catch"](t),this.context)},et["finally"]=function(t){return this.then(function(n){return t.call(this),n},function(n){return t.call(this),nt.reject(n)})};var ot=!1,rt={},it=[],ut=Array.isArray,st=Object.assign||b,ct=document.documentMode,at=document.createElement("a");R.options={url:"",root:null,params:{}},R.transforms=[U,T,w],R.params=function(t){var n=[],e=encodeURIComponent;return n.add=function(t,n){a(n)&&(n=n()),null===n&&(n=""),this.push(e(t)+"="+e(n))},S(n,t),n.join("&").replace(/%20/g,"+")},R.parse=function(t){return ct&&(at.href=t,t=at.href),at.href=t,{href:at.href,protocol:at.protocol?at.protocol.replace(/:$/,""):"",port:at.port,host:at.host,hostname:at.hostname,pathname:"/"===at.pathname.charAt(0)?at.pathname:"/"+at.pathname,search:at.search?at.search.replace(/^\?/,""):"",hash:at.hash?at.hash.replace(/^#/,""):""}};var ft=R.parse(location.href),ht="withCredentials"in new XMLHttpRequest,pt=function(t,n){if(!(t instanceof n))throw new TypeError("Cannot call a class as a function")},lt=function(){function t(n,e){var o=e.url,r=e.headers,i=e.status,u=e.statusText;pt(this,t),this.url=o,this.body=n,this.headers=r||{},this.status=i||0,this.statusText=u||"",this.ok=i>=200&&i<300}return t.prototype.text=function(){return this.body},t.prototype.blob=function(){return new Blob([this.body])},t.prototype.json=function(){return JSON.parse(this.body)},t}(),dt=function(){function t(n){pt(this,t),this.method="GET",this.body=null,this.params={},this.headers={},st(this,n)}return t.prototype.getUrl=function(){return R(this)},t.prototype.getBody=function(){return this.body},t.prototype.respondWith=function(t,n){return new lt(t,st(n||{},{url:this.getUrl()}))},t}(),mt={"X-Requested-With":"XMLHttpRequest"},vt={Accept:"application/json, text/plain, */*"},yt={"Content-Type":"application/json;charset=utf-8"};return V.options={},V.headers={put:yt,post:yt,patch:yt,"delete":yt,custom:mt,common:vt},V.interceptors=[D,X,J,L,N,M,H],["get","delete","head","jsonp"].forEach(function(t){V[t]=function(n,e){return this(st(e||{},{url:n,method:t}))}}),["post","put","patch"].forEach(function(t){V[t]=function(n,e,o){return this(st(o||{},{url:n,method:t,body:e}))}}),_.actions={get:{method:"GET"},save:{method:"POST"},query:{method:"GET"},update:{method:"PUT"},remove:{method:"DELETE"},"delete":{method:"DELETE"}},"undefined"!=typeof window&&window.Vue&&window.Vue.use(K),K}); \ No newline at end of file
diff --git a/vendor/assets/javascripts/vue.full.js b/vendor/assets/javascripts/vue.full.js
deleted file mode 100644
index ea15bfac416..00000000000
--- a/vendor/assets/javascripts/vue.full.js
+++ /dev/null
@@ -1,7515 +0,0 @@
-/*!
- * Vue.js v2.0.3
- * (c) 2014-2016 Evan You
- * Released under the MIT License.
- */
-(function (global, factory) {
- typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
- typeof define === 'function' && define.amd ? define(factory) :
- (global.Vue = factory());
-}(this, (function () { 'use strict';
-
-/* */
-
-/**
- * Convert a value to a string that is actually rendered.
- */
-function _toString (val) {
- return val == null
- ? ''
- : typeof val === 'object'
- ? JSON.stringify(val, null, 2)
- : String(val)
-}
-
-/**
- * Convert a input value to a number for persistence.
- * If the conversion fails, return original string.
- */
-function toNumber (val) {
- var n = parseFloat(val, 10);
- return (n || n === 0) ? n : val
-}
-
-/**
- * Make a map and return a function for checking if a key
- * is in that map.
- */
-function makeMap (
- str,
- expectsLowerCase
-) {
- var map = Object.create(null);
- var list = str.split(',');
- for (var i = 0; i < list.length; i++) {
- map[list[i]] = true;
- }
- return expectsLowerCase
- ? function (val) { return map[val.toLowerCase()]; }
- : function (val) { return map[val]; }
-}
-
-/**
- * Check if a tag is a built-in tag.
- */
-var isBuiltInTag = makeMap('slot,component', true);
-
-/**
- * Remove an item from an array
- */
-function remove$1 (arr, item) {
- if (arr.length) {
- var index = arr.indexOf(item);
- if (index > -1) {
- return arr.splice(index, 1)
- }
- }
-}
-
-/**
- * Check whether the object has the property.
- */
-var hasOwnProperty = Object.prototype.hasOwnProperty;
-function hasOwn (obj, key) {
- return hasOwnProperty.call(obj, key)
-}
-
-/**
- * Check if value is primitive
- */
-function isPrimitive (value) {
- return typeof value === 'string' || typeof value === 'number'
-}
-
-/**
- * Create a cached version of a pure function.
- */
-function cached (fn) {
- var cache = Object.create(null);
- return function cachedFn (str) {
- var hit = cache[str];
- return hit || (cache[str] = fn(str))
- }
-}
-
-/**
- * Camelize a hyphen-delmited string.
- */
-var camelizeRE = /-(\w)/g;
-var camelize = cached(function (str) {
- return str.replace(camelizeRE, function (_, c) { return c ? c.toUpperCase() : ''; })
-});
-
-/**
- * Capitalize a string.
- */
-var capitalize = cached(function (str) {
- return str.charAt(0).toUpperCase() + str.slice(1)
-});
-
-/**
- * Hyphenate a camelCase string.
- */
-var hyphenateRE = /([^-])([A-Z])/g;
-var hyphenate = cached(function (str) {
- return str
- .replace(hyphenateRE, '$1-$2')
- .replace(hyphenateRE, '$1-$2')
- .toLowerCase()
-});
-
-/**
- * Simple bind, faster than native
- */
-function bind$1 (fn, ctx) {
- function boundFn (a) {
- var l = arguments.length;
- return l
- ? l > 1
- ? fn.apply(ctx, arguments)
- : fn.call(ctx, a)
- : fn.call(ctx)
- }
- // record original fn length
- boundFn._length = fn.length;
- return boundFn
-}
-
-/**
- * Convert an Array-like object to a real Array.
- */
-function toArray (list, start) {
- start = start || 0;
- var i = list.length - start;
- var ret = new Array(i);
- while (i--) {
- ret[i] = list[i + start];
- }
- return ret
-}
-
-/**
- * Mix properties into target object.
- */
-function extend (to, _from) {
- for (var key in _from) {
- to[key] = _from[key];
- }
- return to
-}
-
-/**
- * Quick object check - this is primarily used to tell
- * Objects from primitive values when we know the value
- * is a JSON-compliant type.
- */
-function isObject (obj) {
- return obj !== null && typeof obj === 'object'
-}
-
-/**
- * Strict object type check. Only returns true
- * for plain JavaScript objects.
- */
-var toString = Object.prototype.toString;
-var OBJECT_STRING = '[object Object]';
-function isPlainObject (obj) {
- return toString.call(obj) === OBJECT_STRING
-}
-
-/**
- * Merge an Array of Objects into a single Object.
- */
-function toObject (arr) {
- var res = {};
- for (var i = 0; i < arr.length; i++) {
- if (arr[i]) {
- extend(res, arr[i]);
- }
- }
- return res
-}
-
-/**
- * Perform no operation.
- */
-function noop () {}
-
-/**
- * Always return false.
- */
-var no = function () { return false; };
-
-/**
- * Generate a static keys string from compiler modules.
- */
-function genStaticKeys (modules) {
- return modules.reduce(function (keys, m) {
- return keys.concat(m.staticKeys || [])
- }, []).join(',')
-}
-
-/**
- * Check if two values are loosely equal - that is,
- * if they are plain objects, do they have the same shape?
- */
-function looseEqual (a, b) {
- /* eslint-disable eqeqeq */
- return a == b || (
- isObject(a) && isObject(b)
- ? JSON.stringify(a) === JSON.stringify(b)
- : false
- )
- /* eslint-enable eqeqeq */
-}
-
-function looseIndexOf (arr, val) {
- for (var i = 0; i < arr.length; i++) {
- if (looseEqual(arr[i], val)) { return i }
- }
- return -1
-}
-
-/* */
-
-var config = {
- /**
- * Option merge strategies (used in core/util/options)
- */
- optionMergeStrategies: Object.create(null),
-
- /**
- * Whether to suppress warnings.
- */
- silent: false,
-
- /**
- * Whether to enable devtools
- */
- devtools: "development" !== 'production',
-
- /**
- * Error handler for watcher errors
- */
- errorHandler: null,
-
- /**
- * Ignore certain custom elements
- */
- ignoredElements: null,
-
- /**
- * Custom user key aliases for v-on
- */
- keyCodes: Object.create(null),
-
- /**
- * Check if a tag is reserved so that it cannot be registered as a
- * component. This is platform-dependent and may be overwritten.
- */
- isReservedTag: no,
-
- /**
- * Check if a tag is an unknown element.
- * Platform-dependent.
- */
- isUnknownElement: no,
-
- /**
- * Get the namespace of an element
- */
- getTagNamespace: noop,
-
- /**
- * Check if an attribute must be bound using property, e.g. value
- * Platform-dependent.
- */
- mustUseProp: no,
-
- /**
- * List of asset types that a component can own.
- */
- _assetTypes: [
- 'component',
- 'directive',
- 'filter'
- ],
-
- /**
- * List of lifecycle hooks.
- */
- _lifecycleHooks: [
- 'beforeCreate',
- 'created',
- 'beforeMount',
- 'mounted',
- 'beforeUpdate',
- 'updated',
- 'beforeDestroy',
- 'destroyed',
- 'activated',
- 'deactivated'
- ],
-
- /**
- * Max circular updates allowed in a scheduler flush cycle.
- */
- _maxUpdateCount: 100,
-
- /**
- * Server rendering?
- */
- _isServer: "client" === 'server'
-};
-
-/* */
-
-/**
- * Check if a string starts with $ or _
- */
-function isReserved (str) {
- var c = (str + '').charCodeAt(0);
- return c === 0x24 || c === 0x5F
-}
-
-/**
- * Define a property.
- */
-function def (obj, key, val, enumerable) {
- Object.defineProperty(obj, key, {
- value: val,
- enumerable: !!enumerable,
- writable: true,
- configurable: true
- });
-}
-
-/**
- * Parse simple path.
- */
-var bailRE = /[^\w\.\$]/;
-function parsePath (path) {
- if (bailRE.test(path)) {
- return
- } else {
- var segments = path.split('.');
- return function (obj) {
- for (var i = 0; i < segments.length; i++) {
- if (!obj) { return }
- obj = obj[segments[i]];
- }
- return obj
- }
- }
-}
-
-/* */
-/* globals MutationObserver */
-
-// can we use __proto__?
-var hasProto = '__proto__' in {};
-
-// Browser environment sniffing
-var inBrowser =
- typeof window !== 'undefined' &&
- Object.prototype.toString.call(window) !== '[object Object]';
-
-var UA = inBrowser && window.navigator.userAgent.toLowerCase();
-var isIE = UA && /msie|trident/.test(UA);
-var isIE9 = UA && UA.indexOf('msie 9.0') > 0;
-var isEdge = UA && UA.indexOf('edge/') > 0;
-var isAndroid = UA && UA.indexOf('android') > 0;
-var isIOS = UA && /iphone|ipad|ipod|ios/.test(UA);
-
-// detect devtools
-var devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__;
-
-/* istanbul ignore next */
-function isNative (Ctor) {
- return /native code/.test(Ctor.toString())
-}
-
-/**
- * Defer a task to execute it asynchronously.
- */
-var nextTick = (function () {
- var callbacks = [];
- var pending = false;
- var timerFunc;
-
- function nextTickHandler () {
- pending = false;
- var copies = callbacks.slice(0);
- callbacks.length = 0;
- for (var i = 0; i < copies.length; i++) {
- copies[i]();
- }
- }
-
- // the nextTick behavior leverages the microtask queue, which can be accessed
- // via either native Promise.then or MutationObserver.
- // MutationObserver has wider support, however it is seriously bugged in
- // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
- // completely stops working after triggering a few times... so, if native
- // Promise is available, we will use it:
- /* istanbul ignore if */
- if (typeof Promise !== 'undefined' && isNative(Promise)) {
- var p = Promise.resolve();
- timerFunc = function () {
- p.then(nextTickHandler);
- // in problematic UIWebViews, Promise.then doesn't completely break, but
- // it can get stuck in a weird state where callbacks are pushed into the
- // microtask queue but the queue isn't being flushed, until the browser
- // needs to do some other work, e.g. handle a timer. Therefore we can
- // "force" the microtask queue to be flushed by adding an empty timer.
- if (isIOS) { setTimeout(noop); }
- };
- } else if (typeof MutationObserver !== 'undefined' && (
- isNative(MutationObserver) ||
- // PhantomJS and iOS 7.x
- MutationObserver.toString() === '[object MutationObserverConstructor]'
- )) {
- // use MutationObserver where native Promise is not available,
- // e.g. PhantomJS IE11, iOS7, Android 4.4
- var counter = 1;
- var observer = new MutationObserver(nextTickHandler);
- var textNode = document.createTextNode(String(counter));
- observer.observe(textNode, {
- characterData: true
- });
- timerFunc = function () {
- counter = (counter + 1) % 2;
- textNode.data = String(counter);
- };
- } else {
- // fallback to setTimeout
- /* istanbul ignore next */
- timerFunc = function () {
- setTimeout(nextTickHandler, 0);
- };
- }
-
- return function queueNextTick (cb, ctx) {
- var func = ctx
- ? function () { cb.call(ctx); }
- : cb;
- callbacks.push(func);
- if (!pending) {
- pending = true;
- timerFunc();
- }
- }
-})();
-
-var _Set;
-/* istanbul ignore if */
-if (typeof Set !== 'undefined' && isNative(Set)) {
- // use native Set when available.
- _Set = Set;
-} else {
- // a non-standard Set polyfill that only works with primitive keys.
- _Set = (function () {
- function Set () {
- this.set = Object.create(null);
- }
- Set.prototype.has = function has (key) {
- return this.set[key] !== undefined
- };
- Set.prototype.add = function add (key) {
- this.set[key] = 1;
- };
- Set.prototype.clear = function clear () {
- this.set = Object.create(null);
- };
-
- return Set;
- }());
-}
-
-/* not type checking this file because flow doesn't play well with Proxy */
-
-var hasProxy;
-var proxyHandlers;
-var initProxy;
-
-{
- var allowedGlobals = makeMap(
- 'Infinity,undefined,NaN,isFinite,isNaN,' +
- 'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' +
- 'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' +
- 'require' // for Webpack/Browserify
- );
-
- hasProxy =
- typeof Proxy !== 'undefined' &&
- Proxy.toString().match(/native code/);
-
- proxyHandlers = {
- has: function has (target, key) {
- var has = key in target;
- var isAllowed = allowedGlobals(key) || key.charAt(0) === '_';
- if (!has && !isAllowed) {
- warn(
- "Property or method \"" + key + "\" is not defined on the instance but " +
- "referenced during render. Make sure to declare reactive data " +
- "properties in the data option.",
- target
- );
- }
- return has || !isAllowed
- }
- };
-
- initProxy = function initProxy (vm) {
- if (hasProxy) {
- vm._renderProxy = new Proxy(vm, proxyHandlers);
- } else {
- vm._renderProxy = vm;
- }
- };
-}
-
-/* */
-
-
-var uid$2 = 0;
-
-/**
- * A dep is an observable that can have multiple
- * directives subscribing to it.
- */
-var Dep = function Dep () {
- this.id = uid$2++;
- this.subs = [];
-};
-
-Dep.prototype.addSub = function addSub (sub) {
- this.subs.push(sub);
-};
-
-Dep.prototype.removeSub = function removeSub (sub) {
- remove$1(this.subs, sub);
-};
-
-Dep.prototype.depend = function depend () {
- if (Dep.target) {
- Dep.target.addDep(this);
- }
-};
-
-Dep.prototype.notify = function notify () {
- // stablize the subscriber list first
- var subs = this.subs.slice();
- for (var i = 0, l = subs.length; i < l; i++) {
- subs[i].update();
- }
-};
-
-// the current target watcher being evaluated.
-// this is globally unique because there could be only one
-// watcher being evaluated at any time.
-Dep.target = null;
-var targetStack = [];
-
-function pushTarget (_target) {
- if (Dep.target) { targetStack.push(Dep.target); }
- Dep.target = _target;
-}
-
-function popTarget () {
- Dep.target = targetStack.pop();
-}
-
-/* */
-
-
-var queue = [];
-var has$1 = {};
-var circular = {};
-var waiting = false;
-var flushing = false;
-var index = 0;
-
-/**
- * Reset the scheduler's state.
- */
-function resetSchedulerState () {
- queue.length = 0;
- has$1 = {};
- {
- circular = {};
- }
- waiting = flushing = false;
-}
-
-/**
- * Flush both queues and run the watchers.
- */
-function flushSchedulerQueue () {
- flushing = true;
-
- // Sort queue before flush.
- // This ensures that:
- // 1. Components are updated from parent to child. (because parent is always
- // created before the child)
- // 2. A component's user watchers are run before its render watcher (because
- // user watchers are created before the render watcher)
- // 3. If a component is destroyed during a parent component's watcher run,
- // its watchers can be skipped.
- queue.sort(function (a, b) { return a.id - b.id; });
-
- // do not cache length because more watchers might be pushed
- // as we run existing watchers
- for (index = 0; index < queue.length; index++) {
- var watcher = queue[index];
- var id = watcher.id;
- has$1[id] = null;
- watcher.run();
- // in dev build, check and stop circular updates.
- if ("development" !== 'production' && has$1[id] != null) {
- circular[id] = (circular[id] || 0) + 1;
- if (circular[id] > config._maxUpdateCount) {
- warn(
- 'You may have an infinite update loop ' + (
- watcher.user
- ? ("in watcher with expression \"" + (watcher.expression) + "\"")
- : "in a component render function."
- ),
- watcher.vm
- );
- break
- }
- }
- }
-
- // devtool hook
- /* istanbul ignore if */
- if (devtools && config.devtools) {
- devtools.emit('flush');
- }
-
- resetSchedulerState();
-}
-
-/**
- * Push a watcher into the watcher queue.
- * Jobs with duplicate IDs will be skipped unless it's
- * pushed when the queue is being flushed.
- */
-function queueWatcher (watcher) {
- var id = watcher.id;
- if (has$1[id] == null) {
- has$1[id] = true;
- if (!flushing) {
- queue.push(watcher);
- } else {
- // if already flushing, splice the watcher based on its id
- // if already past its id, it will be run next immediately.
- var i = queue.length - 1;
- while (i >= 0 && queue[i].id > watcher.id) {
- i--;
- }
- queue.splice(Math.max(i, index) + 1, 0, watcher);
- }
- // queue the flush
- if (!waiting) {
- waiting = true;
- nextTick(flushSchedulerQueue);
- }
- }
-}
-
-/* */
-
-var uid$1 = 0;
-
-/**
- * A watcher parses an expression, collects dependencies,
- * and fires callback when the expression value changes.
- * This is used for both the $watch() api and directives.
- */
-var Watcher = function Watcher (
- vm,
- expOrFn,
- cb,
- options
-) {
- if ( options === void 0 ) options = {};
-
- this.vm = vm;
- vm._watchers.push(this);
- // options
- this.deep = !!options.deep;
- this.user = !!options.user;
- this.lazy = !!options.lazy;
- this.sync = !!options.sync;
- this.expression = expOrFn.toString();
- this.cb = cb;
- this.id = ++uid$1; // uid for batching
- this.active = true;
- this.dirty = this.lazy; // for lazy watchers
- this.deps = [];
- this.newDeps = [];
- this.depIds = new _Set();
- this.newDepIds = new _Set();
- // parse expression for getter
- if (typeof expOrFn === 'function') {
- this.getter = expOrFn;
- } else {
- this.getter = parsePath(expOrFn);
- if (!this.getter) {
- this.getter = function () {};
- "development" !== 'production' && warn(
- "Failed watching path: \"" + expOrFn + "\" " +
- 'Watcher only accepts simple dot-delimited paths. ' +
- 'For full control, use a function instead.',
- vm
- );
- }
- }
- this.value = this.lazy
- ? undefined
- : this.get();
-};
-
-/**
- * Evaluate the getter, and re-collect dependencies.
- */
-Watcher.prototype.get = function get () {
- pushTarget(this);
- var value = this.getter.call(this.vm, this.vm);
- // "touch" every property so they are all tracked as
- // dependencies for deep watching
- if (this.deep) {
- traverse(value);
- }
- popTarget();
- this.cleanupDeps();
- return value
-};
-
-/**
- * Add a dependency to this directive.
- */
-Watcher.prototype.addDep = function addDep (dep) {
- var id = dep.id;
- if (!this.newDepIds.has(id)) {
- this.newDepIds.add(id);
- this.newDeps.push(dep);
- if (!this.depIds.has(id)) {
- dep.addSub(this);
- }
- }
-};
-
-/**
- * Clean up for dependency collection.
- */
-Watcher.prototype.cleanupDeps = function cleanupDeps () {
- var this$1 = this;
-
- var i = this.deps.length;
- while (i--) {
- var dep = this$1.deps[i];
- if (!this$1.newDepIds.has(dep.id)) {
- dep.removeSub(this$1);
- }
- }
- var tmp = this.depIds;
- this.depIds = this.newDepIds;
- this.newDepIds = tmp;
- this.newDepIds.clear();
- tmp = this.deps;
- this.deps = this.newDeps;
- this.newDeps = tmp;
- this.newDeps.length = 0;
-};
-
-/**
- * Subscriber interface.
- * Will be called when a dependency changes.
- */
-Watcher.prototype.update = function update () {
- /* istanbul ignore else */
- if (this.lazy) {
- this.dirty = true;
- } else if (this.sync) {
- this.run();
- } else {
- queueWatcher(this);
- }
-};
-
-/**
- * Scheduler job interface.
- * Will be called by the scheduler.
- */
-Watcher.prototype.run = function run () {
- if (this.active) {
- var value = this.get();
- if (
- value !== this.value ||
- // Deep watchers and watchers on Object/Arrays should fire even
- // when the value is the same, because the value may
- // have mutated.
- isObject(value) ||
- this.deep
- ) {
- // set new value
- var oldValue = this.value;
- this.value = value;
- if (this.user) {
- try {
- this.cb.call(this.vm, value, oldValue);
- } catch (e) {
- "development" !== 'production' && warn(
- ("Error in watcher \"" + (this.expression) + "\""),
- this.vm
- );
- /* istanbul ignore else */
- if (config.errorHandler) {
- config.errorHandler.call(null, e, this.vm);
- } else {
- throw e
- }
- }
- } else {
- this.cb.call(this.vm, value, oldValue);
- }
- }
- }
-};
-
-/**
- * Evaluate the value of the watcher.
- * This only gets called for lazy watchers.
- */
-Watcher.prototype.evaluate = function evaluate () {
- this.value = this.get();
- this.dirty = false;
-};
-
-/**
- * Depend on all deps collected by this watcher.
- */
-Watcher.prototype.depend = function depend () {
- var this$1 = this;
-
- var i = this.deps.length;
- while (i--) {
- this$1.deps[i].depend();
- }
-};
-
-/**
- * Remove self from all dependencies' subcriber list.
- */
-Watcher.prototype.teardown = function teardown () {
- var this$1 = this;
-
- if (this.active) {
- // remove self from vm's watcher list
- // this is a somewhat expensive operation so we skip it
- // if the vm is being destroyed or is performing a v-for
- // re-render (the watcher list is then filtered by v-for).
- if (!this.vm._isBeingDestroyed && !this.vm._vForRemoving) {
- remove$1(this.vm._watchers, this);
- }
- var i = this.deps.length;
- while (i--) {
- this$1.deps[i].removeSub(this$1);
- }
- this.active = false;
- }
-};
-
-/**
- * Recursively traverse an object to evoke all converted
- * getters, so that every nested property inside the object
- * is collected as a "deep" dependency.
- */
-var seenObjects = new _Set();
-function traverse (val, seen) {
- var i, keys;
- if (!seen) {
- seen = seenObjects;
- seen.clear();
- }
- var isA = Array.isArray(val);
- var isO = isObject(val);
- if ((isA || isO) && Object.isExtensible(val)) {
- if (val.__ob__) {
- var depId = val.__ob__.dep.id;
- if (seen.has(depId)) {
- return
- } else {
- seen.add(depId);
- }
- }
- if (isA) {
- i = val.length;
- while (i--) { traverse(val[i], seen); }
- } else if (isO) {
- keys = Object.keys(val);
- i = keys.length;
- while (i--) { traverse(val[keys[i]], seen); }
- }
- }
-}
-
-/*
- * not type checking this file because flow doesn't play well with
- * dynamically accessing methods on Array prototype
- */
-
-var arrayProto = Array.prototype;
-var arrayMethods = Object.create(arrayProto);[
- 'push',
- 'pop',
- 'shift',
- 'unshift',
- 'splice',
- 'sort',
- 'reverse'
-]
-.forEach(function (method) {
- // cache original method
- var original = arrayProto[method];
- def(arrayMethods, method, function mutator () {
- var arguments$1 = arguments;
-
- // avoid leaking arguments:
- // http://jsperf.com/closure-with-arguments
- var i = arguments.length;
- var args = new Array(i);
- while (i--) {
- args[i] = arguments$1[i];
- }
- var result = original.apply(this, args);
- var ob = this.__ob__;
- var inserted;
- switch (method) {
- case 'push':
- inserted = args;
- break
- case 'unshift':
- inserted = args;
- break
- case 'splice':
- inserted = args.slice(2);
- break
- }
- if (inserted) { ob.observeArray(inserted); }
- // notify change
- ob.dep.notify();
- return result
- });
-});
-
-/* */
-
-var arrayKeys = Object.getOwnPropertyNames(arrayMethods);
-
-/**
- * By default, when a reactive property is set, the new value is
- * also converted to become reactive. However when passing down props,
- * we don't want to force conversion because the value may be a nested value
- * under a frozen data structure. Converting it would defeat the optimization.
- */
-var observerState = {
- shouldConvert: true,
- isSettingProps: false
-};
-
-/**
- * Observer class that are attached to each observed
- * object. Once attached, the observer converts target
- * object's property keys into getter/setters that
- * collect dependencies and dispatches updates.
- */
-var Observer = function Observer (value) {
- this.value = value;
- this.dep = new Dep();
- this.vmCount = 0;
- def(value, '__ob__', this);
- if (Array.isArray(value)) {
- var augment = hasProto
- ? protoAugment
- : copyAugment;
- augment(value, arrayMethods, arrayKeys);
- this.observeArray(value);
- } else {
- this.walk(value);
- }
-};
-
-/**
- * Walk through each property and convert them into
- * getter/setters. This method should only be called when
- * value type is Object.
- */
-Observer.prototype.walk = function walk (obj) {
- var keys = Object.keys(obj);
- for (var i = 0; i < keys.length; i++) {
- defineReactive$$1(obj, keys[i], obj[keys[i]]);
- }
-};
-
-/**
- * Observe a list of Array items.
- */
-Observer.prototype.observeArray = function observeArray (items) {
- for (var i = 0, l = items.length; i < l; i++) {
- observe(items[i]);
- }
-};
-
-// helpers
-
-/**
- * Augment an target Object or Array by intercepting
- * the prototype chain using __proto__
- */
-function protoAugment (target, src) {
- /* eslint-disable no-proto */
- target.__proto__ = src;
- /* eslint-enable no-proto */
-}
-
-/**
- * Augment an target Object or Array by defining
- * hidden properties.
- *
- * istanbul ignore next
- */
-function copyAugment (target, src, keys) {
- for (var i = 0, l = keys.length; i < l; i++) {
- var key = keys[i];
- def(target, key, src[key]);
- }
-}
-
-/**
- * Attempt to create an observer instance for a value,
- * returns the new observer if successfully observed,
- * or the existing observer if the value already has one.
- */
-function observe (value) {
- if (!isObject(value)) {
- return
- }
- var ob;
- if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
- ob = value.__ob__;
- } else if (
- observerState.shouldConvert &&
- !config._isServer &&
- (Array.isArray(value) || isPlainObject(value)) &&
- Object.isExtensible(value) &&
- !value._isVue
- ) {
- ob = new Observer(value);
- }
- return ob
-}
-
-/**
- * Define a reactive property on an Object.
- */
-function defineReactive$$1 (
- obj,
- key,
- val,
- customSetter
-) {
- var dep = new Dep();
-
- var property = Object.getOwnPropertyDescriptor(obj, key);
- if (property && property.configurable === false) {
- return
- }
-
- // cater for pre-defined getter/setters
- var getter = property && property.get;
- var setter = property && property.set;
-
- var childOb = observe(val);
- Object.defineProperty(obj, key, {
- enumerable: true,
- configurable: true,
- get: function reactiveGetter () {
- var value = getter ? getter.call(obj) : val;
- if (Dep.target) {
- dep.depend();
- if (childOb) {
- childOb.dep.depend();
- }
- if (Array.isArray(value)) {
- dependArray(value);
- }
- }
- return value
- },
- set: function reactiveSetter (newVal) {
- var value = getter ? getter.call(obj) : val;
- if (newVal === value) {
- return
- }
- if ("development" !== 'production' && customSetter) {
- customSetter();
- }
- if (setter) {
- setter.call(obj, newVal);
- } else {
- val = newVal;
- }
- childOb = observe(newVal);
- dep.notify();
- }
- });
-}
-
-/**
- * Set a property on an object. Adds the new property and
- * triggers change notification if the property doesn't
- * already exist.
- */
-function set (obj, key, val) {
- if (Array.isArray(obj)) {
- obj.splice(key, 1, val);
- return val
- }
- if (hasOwn(obj, key)) {
- obj[key] = val;
- return
- }
- var ob = obj.__ob__;
- if (obj._isVue || (ob && ob.vmCount)) {
- "development" !== 'production' && warn(
- 'Avoid adding reactive properties to a Vue instance or its root $data ' +
- 'at runtime - declare it upfront in the data option.'
- );
- return
- }
- if (!ob) {
- obj[key] = val;
- return
- }
- defineReactive$$1(ob.value, key, val);
- ob.dep.notify();
- return val
-}
-
-/**
- * Delete a property and trigger change if necessary.
- */
-function del (obj, key) {
- var ob = obj.__ob__;
- if (obj._isVue || (ob && ob.vmCount)) {
- "development" !== 'production' && warn(
- 'Avoid deleting properties on a Vue instance or its root $data ' +
- '- just set it to null.'
- );
- return
- }
- if (!hasOwn(obj, key)) {
- return
- }
- delete obj[key];
- if (!ob) {
- return
- }
- ob.dep.notify();
-}
-
-/**
- * Collect dependencies on array elements when the array is touched, since
- * we cannot intercept array element access like property getters.
- */
-function dependArray (value) {
- for (var e = void 0, i = 0, l = value.length; i < l; i++) {
- e = value[i];
- e && e.__ob__ && e.__ob__.dep.depend();
- if (Array.isArray(e)) {
- dependArray(e);
- }
- }
-}
-
-/* */
-
-function initState (vm) {
- vm._watchers = [];
- initProps(vm);
- initData(vm);
- initComputed(vm);
- initMethods(vm);
- initWatch(vm);
-}
-
-function initProps (vm) {
- var props = vm.$options.props;
- if (props) {
- var propsData = vm.$options.propsData || {};
- var keys = vm.$options._propKeys = Object.keys(props);
- var isRoot = !vm.$parent;
- // root instance props should be converted
- observerState.shouldConvert = isRoot;
- var loop = function ( i ) {
- var key = keys[i];
- /* istanbul ignore else */
- {
- defineReactive$$1(vm, key, validateProp(key, props, propsData, vm), function () {
- if (vm.$parent && !observerState.isSettingProps) {
- warn(
- "Avoid mutating a prop directly since the value will be " +
- "overwritten whenever the parent component re-renders. " +
- "Instead, use a data or computed property based on the prop's " +
- "value. Prop being mutated: \"" + key + "\"",
- vm
- );
- }
- });
- }
- };
-
- for (var i = 0; i < keys.length; i++) loop( i );
- observerState.shouldConvert = true;
- }
-}
-
-function initData (vm) {
- var data = vm.$options.data;
- data = vm._data = typeof data === 'function'
- ? data.call(vm)
- : data || {};
- if (!isPlainObject(data)) {
- data = {};
- "development" !== 'production' && warn(
- 'data functions should return an object.',
- vm
- );
- }
- // proxy data on instance
- var keys = Object.keys(data);
- var props = vm.$options.props;
- var i = keys.length;
- while (i--) {
- if (props && hasOwn(props, keys[i])) {
- "development" !== 'production' && warn(
- "The data property \"" + (keys[i]) + "\" is already declared as a prop. " +
- "Use prop default value instead.",
- vm
- );
- } else {
- proxy(vm, keys[i]);
- }
- }
- // observe data
- observe(data);
- data.__ob__ && data.__ob__.vmCount++;
-}
-
-var computedSharedDefinition = {
- enumerable: true,
- configurable: true,
- get: noop,
- set: noop
-};
-
-function initComputed (vm) {
- var computed = vm.$options.computed;
- if (computed) {
- for (var key in computed) {
- var userDef = computed[key];
- if (typeof userDef === 'function') {
- computedSharedDefinition.get = makeComputedGetter(userDef, vm);
- computedSharedDefinition.set = noop;
- } else {
- computedSharedDefinition.get = userDef.get
- ? userDef.cache !== false
- ? makeComputedGetter(userDef.get, vm)
- : bind$1(userDef.get, vm)
- : noop;
- computedSharedDefinition.set = userDef.set
- ? bind$1(userDef.set, vm)
- : noop;
- }
- Object.defineProperty(vm, key, computedSharedDefinition);
- }
- }
-}
-
-function makeComputedGetter (getter, owner) {
- var watcher = new Watcher(owner, getter, noop, {
- lazy: true
- });
- return function computedGetter () {
- if (watcher.dirty) {
- watcher.evaluate();
- }
- if (Dep.target) {
- watcher.depend();
- }
- return watcher.value
- }
-}
-
-function initMethods (vm) {
- var methods = vm.$options.methods;
- if (methods) {
- for (var key in methods) {
- vm[key] = methods[key] == null ? noop : bind$1(methods[key], vm);
- if ("development" !== 'production' && methods[key] == null) {
- warn(
- "method \"" + key + "\" has an undefined value in the component definition. " +
- "Did you reference the function correctly?",
- vm
- );
- }
- }
- }
-}
-
-function initWatch (vm) {
- var watch = vm.$options.watch;
- if (watch) {
- for (var key in watch) {
- var handler = watch[key];
- if (Array.isArray(handler)) {
- for (var i = 0; i < handler.length; i++) {
- createWatcher(vm, key, handler[i]);
- }
- } else {
- createWatcher(vm, key, handler);
- }
- }
- }
-}
-
-function createWatcher (vm, key, handler) {
- var options;
- if (isPlainObject(handler)) {
- options = handler;
- handler = handler.handler;
- }
- if (typeof handler === 'string') {
- handler = vm[handler];
- }
- vm.$watch(key, handler, options);
-}
-
-function stateMixin (Vue) {
- // flow somehow has problems with directly declared definition object
- // when using Object.defineProperty, so we have to procedurally build up
- // the object here.
- var dataDef = {};
- dataDef.get = function () {
- return this._data
- };
- {
- dataDef.set = function (newData) {
- warn(
- 'Avoid replacing instance root $data. ' +
- 'Use nested data properties instead.',
- this
- );
- };
- }
- Object.defineProperty(Vue.prototype, '$data', dataDef);
-
- Vue.prototype.$set = set;
- Vue.prototype.$delete = del;
-
- Vue.prototype.$watch = function (
- expOrFn,
- cb,
- options
- ) {
- var vm = this;
- options = options || {};
- options.user = true;
- var watcher = new Watcher(vm, expOrFn, cb, options);
- if (options.immediate) {
- cb.call(vm, watcher.value);
- }
- return function unwatchFn () {
- watcher.teardown();
- }
- };
-}
-
-function proxy (vm, key) {
- if (!isReserved(key)) {
- Object.defineProperty(vm, key, {
- configurable: true,
- enumerable: true,
- get: function proxyGetter () {
- return vm._data[key]
- },
- set: function proxySetter (val) {
- vm._data[key] = val;
- }
- });
- }
-}
-
-/* */
-
-var VNode = function VNode (
- tag,
- data,
- children,
- text,
- elm,
- ns,
- context,
- componentOptions
-) {
- this.tag = tag;
- this.data = data;
- this.children = children;
- this.text = text;
- this.elm = elm;
- this.ns = ns;
- this.context = context;
- this.functionalContext = undefined;
- this.key = data && data.key;
- this.componentOptions = componentOptions;
- this.child = undefined;
- this.parent = undefined;
- this.raw = false;
- this.isStatic = false;
- this.isRootInsert = true;
- this.isComment = false;
- this.isCloned = false;
-};
-
-var emptyVNode = function () {
- var node = new VNode();
- node.text = '';
- node.isComment = true;
- return node
-};
-
-// optimized shallow clone
-// used for static nodes and slot nodes because they may be reused across
-// multiple renders, cloning them avoids errors when DOM manipulations rely
-// on their elm reference.
-function cloneVNode (vnode) {
- var cloned = new VNode(
- vnode.tag,
- vnode.data,
- vnode.children,
- vnode.text,
- vnode.elm,
- vnode.ns,
- vnode.context,
- vnode.componentOptions
- );
- cloned.isStatic = vnode.isStatic;
- cloned.key = vnode.key;
- cloned.isCloned = true;
- return cloned
-}
-
-function cloneVNodes (vnodes) {
- var res = new Array(vnodes.length);
- for (var i = 0; i < vnodes.length; i++) {
- res[i] = cloneVNode(vnodes[i]);
- }
- return res
-}
-
-/* */
-
-function mergeVNodeHook (def, hookKey, hook, key) {
- key = key + hookKey;
- var injectedHash = def.__injected || (def.__injected = {});
- if (!injectedHash[key]) {
- injectedHash[key] = true;
- var oldHook = def[hookKey];
- if (oldHook) {
- def[hookKey] = function () {
- oldHook.apply(this, arguments);
- hook.apply(this, arguments);
- };
- } else {
- def[hookKey] = hook;
- }
- }
-}
-
-/* */
-
-function updateListeners (
- on,
- oldOn,
- add,
- remove$$1,
- vm
-) {
- var name, cur, old, fn, event, capture;
- for (name in on) {
- cur = on[name];
- old = oldOn[name];
- if (!cur) {
- "development" !== 'production' && warn(
- "Invalid handler for event \"" + name + "\": got " + String(cur),
- vm
- );
- } else if (!old) {
- capture = name.charAt(0) === '!';
- event = capture ? name.slice(1) : name;
- if (Array.isArray(cur)) {
- add(event, (cur.invoker = arrInvoker(cur)), capture);
- } else {
- if (!cur.invoker) {
- fn = cur;
- cur = on[name] = {};
- cur.fn = fn;
- cur.invoker = fnInvoker(cur);
- }
- add(event, cur.invoker, capture);
- }
- } else if (cur !== old) {
- if (Array.isArray(old)) {
- old.length = cur.length;
- for (var i = 0; i < old.length; i++) { old[i] = cur[i]; }
- on[name] = old;
- } else {
- old.fn = cur;
- on[name] = old;
- }
- }
- }
- for (name in oldOn) {
- if (!on[name]) {
- event = name.charAt(0) === '!' ? name.slice(1) : name;
- remove$$1(event, oldOn[name].invoker);
- }
- }
-}
-
-function arrInvoker (arr) {
- return function (ev) {
- var arguments$1 = arguments;
-
- var single = arguments.length === 1;
- for (var i = 0; i < arr.length; i++) {
- single ? arr[i](ev) : arr[i].apply(null, arguments$1);
- }
- }
-}
-
-function fnInvoker (o) {
- return function (ev) {
- var single = arguments.length === 1;
- single ? o.fn(ev) : o.fn.apply(null, arguments);
- }
-}
-
-/* */
-
-function normalizeChildren (
- children,
- ns,
- nestedIndex
-) {
- if (isPrimitive(children)) {
- return [createTextVNode(children)]
- }
- if (Array.isArray(children)) {
- var res = [];
- for (var i = 0, l = children.length; i < l; i++) {
- var c = children[i];
- var last = res[res.length - 1];
- // nested
- if (Array.isArray(c)) {
- res.push.apply(res, normalizeChildren(c, ns, ((nestedIndex || '') + "_" + i)));
- } else if (isPrimitive(c)) {
- if (last && last.text) {
- last.text += String(c);
- } else if (c !== '') {
- // convert primitive to vnode
- res.push(createTextVNode(c));
- }
- } else if (c instanceof VNode) {
- if (c.text && last && last.text) {
- last.text += c.text;
- } else {
- // inherit parent namespace
- if (ns) {
- applyNS(c, ns);
- }
- // default key for nested array children (likely generated by v-for)
- if (c.tag && c.key == null && nestedIndex != null) {
- c.key = "__vlist" + nestedIndex + "_" + i + "__";
- }
- res.push(c);
- }
- }
- }
- return res
- }
-}
-
-function createTextVNode (val) {
- return new VNode(undefined, undefined, undefined, String(val))
-}
-
-function applyNS (vnode, ns) {
- if (vnode.tag && !vnode.ns) {
- vnode.ns = ns;
- if (vnode.children) {
- for (var i = 0, l = vnode.children.length; i < l; i++) {
- applyNS(vnode.children[i], ns);
- }
- }
- }
-}
-
-/* */
-
-function getFirstComponentChild (children) {
- return children && children.filter(function (c) { return c && c.componentOptions; })[0]
-}
-
-/* */
-
-var activeInstance = null;
-
-function initLifecycle (vm) {
- var options = vm.$options;
-
- // locate first non-abstract parent
- var parent = options.parent;
- if (parent && !options.abstract) {
- while (parent.$options.abstract && parent.$parent) {
- parent = parent.$parent;
- }
- parent.$children.push(vm);
- }
-
- vm.$parent = parent;
- vm.$root = parent ? parent.$root : vm;
-
- vm.$children = [];
- vm.$refs = {};
-
- vm._watcher = null;
- vm._inactive = false;
- vm._isMounted = false;
- vm._isDestroyed = false;
- vm._isBeingDestroyed = false;
-}
-
-function lifecycleMixin (Vue) {
- Vue.prototype._mount = function (
- el,
- hydrating
- ) {
- var vm = this;
- vm.$el = el;
- if (!vm.$options.render) {
- vm.$options.render = emptyVNode;
- {
- /* istanbul ignore if */
- if (vm.$options.template) {
- warn(
- 'You are using the runtime-only build of Vue where the template ' +
- 'option is not available. Either pre-compile the templates into ' +
- 'render functions, or use the compiler-included build.',
- vm
- );
- } else {
- warn(
- 'Failed to mount component: template or render function not defined.',
- vm
- );
- }
- }
- }
- callHook(vm, 'beforeMount');
- vm._watcher = new Watcher(vm, function () {
- vm._update(vm._render(), hydrating);
- }, noop);
- hydrating = false;
- // manually mounted instance, call mounted on self
- // mounted is called for render-created child components in its inserted hook
- if (vm.$vnode == null) {
- vm._isMounted = true;
- callHook(vm, 'mounted');
- }
- return vm
- };
-
- Vue.prototype._update = function (vnode, hydrating) {
- var vm = this;
- if (vm._isMounted) {
- callHook(vm, 'beforeUpdate');
- }
- var prevEl = vm.$el;
- var prevActiveInstance = activeInstance;
- activeInstance = vm;
- var prevVnode = vm._vnode;
- vm._vnode = vnode;
- if (!prevVnode) {
- // Vue.prototype.__patch__ is injected in entry points
- // based on the rendering backend used.
- vm.$el = vm.__patch__(vm.$el, vnode, hydrating);
- } else {
- vm.$el = vm.__patch__(prevVnode, vnode);
- }
- activeInstance = prevActiveInstance;
- // update __vue__ reference
- if (prevEl) {
- prevEl.__vue__ = null;
- }
- if (vm.$el) {
- vm.$el.__vue__ = vm;
- }
- // if parent is an HOC, update its $el as well
- if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
- vm.$parent.$el = vm.$el;
- }
- if (vm._isMounted) {
- callHook(vm, 'updated');
- }
- };
-
- Vue.prototype._updateFromParent = function (
- propsData,
- listeners,
- parentVnode,
- renderChildren
- ) {
- var vm = this;
- var hasChildren = !!(vm.$options._renderChildren || renderChildren);
- vm.$options._parentVnode = parentVnode;
- vm.$options._renderChildren = renderChildren;
- // update props
- if (propsData && vm.$options.props) {
- observerState.shouldConvert = false;
- {
- observerState.isSettingProps = true;
- }
- var propKeys = vm.$options._propKeys || [];
- for (var i = 0; i < propKeys.length; i++) {
- var key = propKeys[i];
- vm[key] = validateProp(key, vm.$options.props, propsData, vm);
- }
- observerState.shouldConvert = true;
- {
- observerState.isSettingProps = false;
- }
- }
- // update listeners
- if (listeners) {
- var oldListeners = vm.$options._parentListeners;
- vm.$options._parentListeners = listeners;
- vm._updateListeners(listeners, oldListeners);
- }
- // resolve slots + force update if has children
- if (hasChildren) {
- vm.$slots = resolveSlots(renderChildren, vm._renderContext);
- vm.$forceUpdate();
- }
- };
-
- Vue.prototype.$forceUpdate = function () {
- var vm = this;
- if (vm._watcher) {
- vm._watcher.update();
- }
- };
-
- Vue.prototype.$destroy = function () {
- var vm = this;
- if (vm._isBeingDestroyed) {
- return
- }
- callHook(vm, 'beforeDestroy');
- vm._isBeingDestroyed = true;
- // remove self from parent
- var parent = vm.$parent;
- if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
- remove$1(parent.$children, vm);
- }
- // teardown watchers
- if (vm._watcher) {
- vm._watcher.teardown();
- }
- var i = vm._watchers.length;
- while (i--) {
- vm._watchers[i].teardown();
- }
- // remove reference from data ob
- // frozen object may not have observer.
- if (vm._data.__ob__) {
- vm._data.__ob__.vmCount--;
- }
- // call the last hook...
- vm._isDestroyed = true;
- callHook(vm, 'destroyed');
- // turn off all instance listeners.
- vm.$off();
- // remove __vue__ reference
- if (vm.$el) {
- vm.$el.__vue__ = null;
- }
- // invoke destroy hooks on current rendered tree
- vm.__patch__(vm._vnode, null);
- };
-}
-
-function callHook (vm, hook) {
- var handlers = vm.$options[hook];
- if (handlers) {
- for (var i = 0, j = handlers.length; i < j; i++) {
- handlers[i].call(vm);
- }
- }
- vm.$emit('hook:' + hook);
-}
-
-/* */
-
-var hooks = { init: init, prepatch: prepatch, insert: insert, destroy: destroy$1 };
-var hooksToMerge = Object.keys(hooks);
-
-function createComponent (
- Ctor,
- data,
- context,
- children,
- tag
-) {
- if (!Ctor) {
- return
- }
-
- if (isObject(Ctor)) {
- Ctor = Vue$3.extend(Ctor);
- }
-
- if (typeof Ctor !== 'function') {
- {
- warn(("Invalid Component definition: " + (String(Ctor))), context);
- }
- return
- }
-
- // async component
- if (!Ctor.cid) {
- if (Ctor.resolved) {
- Ctor = Ctor.resolved;
- } else {
- Ctor = resolveAsyncComponent(Ctor, function () {
- // it's ok to queue this on every render because
- // $forceUpdate is buffered by the scheduler.
- context.$forceUpdate();
- });
- if (!Ctor) {
- // return nothing if this is indeed an async component
- // wait for the callback to trigger parent update.
- return
- }
- }
- }
-
- data = data || {};
-
- // extract props
- var propsData = extractProps(data, Ctor);
-
- // functional component
- if (Ctor.options.functional) {
- return createFunctionalComponent(Ctor, propsData, data, context, children)
- }
-
- // extract listeners, since these needs to be treated as
- // child component listeners instead of DOM listeners
- var listeners = data.on;
- // replace with listeners with .native modifier
- data.on = data.nativeOn;
-
- if (Ctor.options.abstract) {
- // abstract components do not keep anything
- // other than props & listeners
- data = {};
- }
-
- // merge component management hooks onto the placeholder node
- mergeHooks(data);
-
- // return a placeholder vnode
- var name = Ctor.options.name || tag;
- var vnode = new VNode(
- ("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')),
- data, undefined, undefined, undefined, undefined, context,
- { Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children }
- );
- return vnode
-}
-
-function createFunctionalComponent (
- Ctor,
- propsData,
- data,
- context,
- children
-) {
- var props = {};
- var propOptions = Ctor.options.props;
- if (propOptions) {
- for (var key in propOptions) {
- props[key] = validateProp(key, propOptions, propsData);
- }
- }
- var vnode = Ctor.options.render.call(
- null,
- // ensure the createElement function in functional components
- // gets a unique context - this is necessary for correct named slot check
- bind$1(createElement, { _self: Object.create(context) }),
- {
- props: props,
- data: data,
- parent: context,
- children: normalizeChildren(children),
- slots: function () { return resolveSlots(children, context); }
- }
- );
- if (vnode instanceof VNode) {
- vnode.functionalContext = context;
- if (data.slot) {
- (vnode.data || (vnode.data = {})).slot = data.slot;
- }
- }
- return vnode
-}
-
-function createComponentInstanceForVnode (
- vnode, // we know it's MountedComponentVNode but flow doesn't
- parent // activeInstance in lifecycle state
-) {
- var vnodeComponentOptions = vnode.componentOptions;
- var options = {
- _isComponent: true,
- parent: parent,
- propsData: vnodeComponentOptions.propsData,
- _componentTag: vnodeComponentOptions.tag,
- _parentVnode: vnode,
- _parentListeners: vnodeComponentOptions.listeners,
- _renderChildren: vnodeComponentOptions.children
- };
- // check inline-template render functions
- var inlineTemplate = vnode.data.inlineTemplate;
- if (inlineTemplate) {
- options.render = inlineTemplate.render;
- options.staticRenderFns = inlineTemplate.staticRenderFns;
- }
- return new vnodeComponentOptions.Ctor(options)
-}
-
-function init (vnode, hydrating) {
- if (!vnode.child || vnode.child._isDestroyed) {
- var child = vnode.child = createComponentInstanceForVnode(vnode, activeInstance);
- child.$mount(hydrating ? vnode.elm : undefined, hydrating);
- }
-}
-
-function prepatch (
- oldVnode,
- vnode
-) {
- var options = vnode.componentOptions;
- var child = vnode.child = oldVnode.child;
- child._updateFromParent(
- options.propsData, // updated props
- options.listeners, // updated listeners
- vnode, // new parent vnode
- options.children // new children
- );
-}
-
-function insert (vnode) {
- if (!vnode.child._isMounted) {
- vnode.child._isMounted = true;
- callHook(vnode.child, 'mounted');
- }
- if (vnode.data.keepAlive) {
- vnode.child._inactive = false;
- callHook(vnode.child, 'activated');
- }
-}
-
-function destroy$1 (vnode) {
- if (!vnode.child._isDestroyed) {
- if (!vnode.data.keepAlive) {
- vnode.child.$destroy();
- } else {
- vnode.child._inactive = true;
- callHook(vnode.child, 'deactivated');
- }
- }
-}
-
-function resolveAsyncComponent (
- factory,
- cb
-) {
- if (factory.requested) {
- // pool callbacks
- factory.pendingCallbacks.push(cb);
- } else {
- factory.requested = true;
- var cbs = factory.pendingCallbacks = [cb];
- var sync = true;
-
- var resolve = function (res) {
- if (isObject(res)) {
- res = Vue$3.extend(res);
- }
- // cache resolved
- factory.resolved = res;
- // invoke callbacks only if this is not a synchronous resolve
- // (async resolves are shimmed as synchronous during SSR)
- if (!sync) {
- for (var i = 0, l = cbs.length; i < l; i++) {
- cbs[i](res);
- }
- }
- };
-
- var reject = function (reason) {
- "development" !== 'production' && warn(
- "Failed to resolve async component: " + (String(factory)) +
- (reason ? ("\nReason: " + reason) : '')
- );
- };
-
- var res = factory(resolve, reject);
-
- // handle promise
- if (res && typeof res.then === 'function' && !factory.resolved) {
- res.then(resolve, reject);
- }
-
- sync = false;
- // return in case resolved synchronously
- return factory.resolved
- }
-}
-
-function extractProps (data, Ctor) {
- // we are only extrating raw values here.
- // validation and default values are handled in the child
- // component itself.
- var propOptions = Ctor.options.props;
- if (!propOptions) {
- return
- }
- var res = {};
- var attrs = data.attrs;
- var props = data.props;
- var domProps = data.domProps;
- if (attrs || props || domProps) {
- for (var key in propOptions) {
- var altKey = hyphenate(key);
- checkProp(res, props, key, altKey, true) ||
- checkProp(res, attrs, key, altKey) ||
- checkProp(res, domProps, key, altKey);
- }
- }
- return res
-}
-
-function checkProp (
- res,
- hash,
- key,
- altKey,
- preserve
-) {
- if (hash) {
- if (hasOwn(hash, key)) {
- res[key] = hash[key];
- if (!preserve) {
- delete hash[key];
- }
- return true
- } else if (hasOwn(hash, altKey)) {
- res[key] = hash[altKey];
- if (!preserve) {
- delete hash[altKey];
- }
- return true
- }
- }
- return false
-}
-
-function mergeHooks (data) {
- if (!data.hook) {
- data.hook = {};
- }
- for (var i = 0; i < hooksToMerge.length; i++) {
- var key = hooksToMerge[i];
- var fromParent = data.hook[key];
- var ours = hooks[key];
- data.hook[key] = fromParent ? mergeHook$1(ours, fromParent) : ours;
- }
-}
-
-function mergeHook$1 (a, b) {
- // since all hooks have at most two args, use fixed args
- // to avoid having to use fn.apply().
- return function (_, __) {
- a(_, __);
- b(_, __);
- }
-}
-
-/* */
-
-// wrapper function for providing a more flexible interface
-// without getting yelled at by flow
-function createElement (
- tag,
- data,
- children
-) {
- if (data && (Array.isArray(data) || typeof data !== 'object')) {
- children = data;
- data = undefined;
- }
- // make sure to use real instance instead of proxy as context
- return _createElement(this._self, tag, data, children)
-}
-
-function _createElement (
- context,
- tag,
- data,
- children
-) {
- if (data && data.__ob__) {
- "development" !== 'production' && warn(
- "Avoid using observed data object as vnode data: " + (JSON.stringify(data)) + "\n" +
- 'Always create fresh vnode data objects in each render!',
- context
- );
- return
- }
- if (!tag) {
- // in case of component :is set to falsy value
- return emptyVNode()
- }
- if (typeof tag === 'string') {
- var Ctor;
- var ns = config.getTagNamespace(tag);
- if (config.isReservedTag(tag)) {
- // platform built-in elements
- return new VNode(
- tag, data, normalizeChildren(children, ns),
- undefined, undefined, ns, context
- )
- } else if ((Ctor = resolveAsset(context.$options, 'components', tag))) {
- // component
- return createComponent(Ctor, data, context, children, tag)
- } else {
- // unknown or unlisted namespaced elements
- // check at runtime because it may get assigned a namespace when its
- // parent normalizes children
- return new VNode(
- tag, data, normalizeChildren(children, ns),
- undefined, undefined, ns, context
- )
- }
- } else {
- // direct component options / constructor
- return createComponent(tag, data, context, children)
- }
-}
-
-/* */
-
-function initRender (vm) {
- vm.$vnode = null; // the placeholder node in parent tree
- vm._vnode = null; // the root of the child tree
- vm._staticTrees = null;
- vm._renderContext = vm.$options._parentVnode && vm.$options._parentVnode.context;
- vm.$slots = resolveSlots(vm.$options._renderChildren, vm._renderContext);
- // bind the public createElement fn to this instance
- // so that we get proper render context inside it.
- vm.$createElement = bind$1(createElement, vm);
- if (vm.$options.el) {
- vm.$mount(vm.$options.el);
- }
-}
-
-function renderMixin (Vue) {
- Vue.prototype.$nextTick = function (fn) {
- nextTick(fn, this);
- };
-
- Vue.prototype._render = function () {
- var vm = this;
- var ref = vm.$options;
- var render = ref.render;
- var staticRenderFns = ref.staticRenderFns;
- var _parentVnode = ref._parentVnode;
-
- if (vm._isMounted) {
- // clone slot nodes on re-renders
- for (var key in vm.$slots) {
- vm.$slots[key] = cloneVNodes(vm.$slots[key]);
- }
- }
-
- if (staticRenderFns && !vm._staticTrees) {
- vm._staticTrees = [];
- }
- // set parent vnode. this allows render functions to have access
- // to the data on the placeholder node.
- vm.$vnode = _parentVnode;
- // render self
- var vnode;
- try {
- vnode = render.call(vm._renderProxy, vm.$createElement);
- } catch (e) {
- {
- warn(("Error when rendering " + (formatComponentName(vm)) + ":"));
- }
- /* istanbul ignore else */
- if (config.errorHandler) {
- config.errorHandler.call(null, e, vm);
- } else {
- if (config._isServer) {
- throw e
- } else {
- setTimeout(function () { throw e }, 0);
- }
- }
- // return previous vnode to prevent render error causing blank component
- vnode = vm._vnode;
- }
- // return empty vnode in case the render function errored out
- if (!(vnode instanceof VNode)) {
- if ("development" !== 'production' && Array.isArray(vnode)) {
- warn(
- 'Multiple root nodes returned from render function. Render function ' +
- 'should return a single root node.',
- vm
- );
- }
- vnode = emptyVNode();
- }
- // set parent
- vnode.parent = _parentVnode;
- return vnode
- };
-
- // shorthands used in render functions
- Vue.prototype._h = createElement;
- // toString for mustaches
- Vue.prototype._s = _toString;
- // number conversion
- Vue.prototype._n = toNumber;
- // empty vnode
- Vue.prototype._e = emptyVNode;
- // loose equal
- Vue.prototype._q = looseEqual;
- // loose indexOf
- Vue.prototype._i = looseIndexOf;
-
- // render static tree by index
- Vue.prototype._m = function renderStatic (
- index,
- isInFor
- ) {
- var tree = this._staticTrees[index];
- // if has already-rendered static tree and not inside v-for,
- // we can reuse the same tree by doing a shallow clone.
- if (tree && !isInFor) {
- return Array.isArray(tree)
- ? cloneVNodes(tree)
- : cloneVNode(tree)
- }
- // otherwise, render a fresh tree.
- tree = this._staticTrees[index] = this.$options.staticRenderFns[index].call(this._renderProxy);
- if (Array.isArray(tree)) {
- for (var i = 0; i < tree.length; i++) {
- if (typeof tree[i] !== 'string') {
- tree[i].isStatic = true;
- tree[i].key = "__static__" + index + "_" + i;
- }
- }
- } else {
- tree.isStatic = true;
- tree.key = "__static__" + index;
- }
- return tree
- };
-
- // filter resolution helper
- var identity = function (_) { return _; };
- Vue.prototype._f = function resolveFilter (id) {
- return resolveAsset(this.$options, 'filters', id, true) || identity
- };
-
- // render v-for
- Vue.prototype._l = function renderList (
- val,
- render
- ) {
- var ret, i, l, keys, key;
- if (Array.isArray(val)) {
- ret = new Array(val.length);
- for (i = 0, l = val.length; i < l; i++) {
- ret[i] = render(val[i], i);
- }
- } else if (typeof val === 'number') {
- ret = new Array(val);
- for (i = 0; i < val; i++) {
- ret[i] = render(i + 1, i);
- }
- } else if (isObject(val)) {
- keys = Object.keys(val);
- ret = new Array(keys.length);
- for (i = 0, l = keys.length; i < l; i++) {
- key = keys[i];
- ret[i] = render(val[key], key, i);
- }
- }
- return ret
- };
-
- // renderSlot
- Vue.prototype._t = function (
- name,
- fallback
- ) {
- var slotNodes = this.$slots[name];
- // warn duplicate slot usage
- if (slotNodes && "development" !== 'production') {
- slotNodes._rendered && warn(
- "Duplicate presence of slot \"" + name + "\" found in the same render tree " +
- "- this will likely cause render errors.",
- this
- );
- slotNodes._rendered = true;
- }
- return slotNodes || fallback
- };
-
- // apply v-bind object
- Vue.prototype._b = function bindProps (
- data,
- value,
- asProp
- ) {
- if (value) {
- if (!isObject(value)) {
- "development" !== 'production' && warn(
- 'v-bind without argument expects an Object or Array value',
- this
- );
- } else {
- if (Array.isArray(value)) {
- value = toObject(value);
- }
- for (var key in value) {
- if (key === 'class' || key === 'style') {
- data[key] = value[key];
- } else {
- var hash = asProp || config.mustUseProp(key)
- ? data.domProps || (data.domProps = {})
- : data.attrs || (data.attrs = {});
- hash[key] = value[key];
- }
- }
- }
- }
- return data
- };
-
- // expose v-on keyCodes
- Vue.prototype._k = function getKeyCodes (key) {
- return config.keyCodes[key]
- };
-}
-
-function resolveSlots (
- renderChildren,
- context
-) {
- var slots = {};
- if (!renderChildren) {
- return slots
- }
- var children = normalizeChildren(renderChildren) || [];
- var defaultSlot = [];
- var name, child;
- for (var i = 0, l = children.length; i < l; i++) {
- child = children[i];
- // named slots should only be respected if the vnode was rendered in the
- // same context.
- if ((child.context === context || child.functionalContext === context) &&
- child.data && (name = child.data.slot)) {
- var slot = (slots[name] || (slots[name] = []));
- if (child.tag === 'template') {
- slot.push.apply(slot, child.children);
- } else {
- slot.push(child);
- }
- } else {
- defaultSlot.push(child);
- }
- }
- // ignore single whitespace
- if (defaultSlot.length && !(
- defaultSlot.length === 1 &&
- (defaultSlot[0].text === ' ' || defaultSlot[0].isComment)
- )) {
- slots.default = defaultSlot;
- }
- return slots
-}
-
-/* */
-
-function initEvents (vm) {
- vm._events = Object.create(null);
- // init parent attached events
- var listeners = vm.$options._parentListeners;
- var on = bind$1(vm.$on, vm);
- var off = bind$1(vm.$off, vm);
- vm._updateListeners = function (listeners, oldListeners) {
- updateListeners(listeners, oldListeners || {}, on, off, vm);
- };
- if (listeners) {
- vm._updateListeners(listeners);
- }
-}
-
-function eventsMixin (Vue) {
- Vue.prototype.$on = function (event, fn) {
- var vm = this;(vm._events[event] || (vm._events[event] = [])).push(fn);
- return vm
- };
-
- Vue.prototype.$once = function (event, fn) {
- var vm = this;
- function on () {
- vm.$off(event, on);
- fn.apply(vm, arguments);
- }
- on.fn = fn;
- vm.$on(event, on);
- return vm
- };
-
- Vue.prototype.$off = function (event, fn) {
- var vm = this;
- // all
- if (!arguments.length) {
- vm._events = Object.create(null);
- return vm
- }
- // specific event
- var cbs = vm._events[event];
- if (!cbs) {
- return vm
- }
- if (arguments.length === 1) {
- vm._events[event] = null;
- return vm
- }
- // specific handler
- var cb;
- var i = cbs.length;
- while (i--) {
- cb = cbs[i];
- if (cb === fn || cb.fn === fn) {
- cbs.splice(i, 1);
- break
- }
- }
- return vm
- };
-
- Vue.prototype.$emit = function (event) {
- var vm = this;
- var cbs = vm._events[event];
- if (cbs) {
- cbs = cbs.length > 1 ? toArray(cbs) : cbs;
- var args = toArray(arguments, 1);
- for (var i = 0, l = cbs.length; i < l; i++) {
- cbs[i].apply(vm, args);
- }
- }
- return vm
- };
-}
-
-/* */
-
-var uid = 0;
-
-function initMixin (Vue) {
- Vue.prototype._init = function (options) {
- var vm = this;
- // a uid
- vm._uid = uid++;
- // a flag to avoid this being observed
- vm._isVue = true;
- // merge options
- if (options && options._isComponent) {
- // optimize internal component instantiation
- // since dynamic options merging is pretty slow, and none of the
- // internal component options needs special treatment.
- initInternalComponent(vm, options);
- } else {
- vm.$options = mergeOptions(
- resolveConstructorOptions(vm),
- options || {},
- vm
- );
- }
- /* istanbul ignore else */
- {
- initProxy(vm);
- }
- // expose real self
- vm._self = vm;
- initLifecycle(vm);
- initEvents(vm);
- callHook(vm, 'beforeCreate');
- initState(vm);
- callHook(vm, 'created');
- initRender(vm);
- };
-
- function initInternalComponent (vm, options) {
- var opts = vm.$options = Object.create(resolveConstructorOptions(vm));
- // doing this because it's faster than dynamic enumeration.
- opts.parent = options.parent;
- opts.propsData = options.propsData;
- opts._parentVnode = options._parentVnode;
- opts._parentListeners = options._parentListeners;
- opts._renderChildren = options._renderChildren;
- opts._componentTag = options._componentTag;
- if (options.render) {
- opts.render = options.render;
- opts.staticRenderFns = options.staticRenderFns;
- }
- }
-
- function resolveConstructorOptions (vm) {
- var Ctor = vm.constructor;
- var options = Ctor.options;
- if (Ctor.super) {
- var superOptions = Ctor.super.options;
- var cachedSuperOptions = Ctor.superOptions;
- if (superOptions !== cachedSuperOptions) {
- // super option changed
- Ctor.superOptions = superOptions;
- options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions);
- if (options.name) {
- options.components[options.name] = Ctor;
- }
- }
- }
- return options
- }
-}
-
-function Vue$3 (options) {
- if ("development" !== 'production' &&
- !(this instanceof Vue$3)) {
- warn('Vue is a constructor and should be called with the `new` keyword');
- }
- this._init(options);
-}
-
-initMixin(Vue$3);
-stateMixin(Vue$3);
-eventsMixin(Vue$3);
-lifecycleMixin(Vue$3);
-renderMixin(Vue$3);
-
-var warn = noop;
-var formatComponentName;
-
-{
- var hasConsole = typeof console !== 'undefined';
-
- warn = function (msg, vm) {
- if (hasConsole && (!config.silent)) {
- console.error("[Vue warn]: " + msg + " " + (
- vm ? formatLocation(formatComponentName(vm)) : ''
- ));
- }
- };
-
- formatComponentName = function (vm) {
- if (vm.$root === vm) {
- return 'root instance'
- }
- var name = vm._isVue
- ? vm.$options.name || vm.$options._componentTag
- : vm.name;
- return (
- (name ? ("component <" + name + ">") : "anonymous component") +
- (vm._isVue && vm.$options.__file ? (" at " + (vm.$options.__file)) : '')
- )
- };
-
- var formatLocation = function (str) {
- if (str === 'anonymous component') {
- str += " - use the \"name\" option for better debugging messages.";
- }
- return ("\n(found in " + str + ")")
- };
-}
-
-/* */
-
-/**
- * Option overwriting strategies are functions that handle
- * how to merge a parent option value and a child option
- * value into the final value.
- */
-var strats = config.optionMergeStrategies;
-
-/**
- * Options with restrictions
- */
-{
- strats.el = strats.propsData = function (parent, child, vm, key) {
- if (!vm) {
- warn(
- "option \"" + key + "\" can only be used during instance " +
- 'creation with the `new` keyword.'
- );
- }
- return defaultStrat(parent, child)
- };
-}
-
-/**
- * Helper that recursively merges two data objects together.
- */
-function mergeData (to, from) {
- var key, toVal, fromVal;
- for (key in from) {
- toVal = to[key];
- fromVal = from[key];
- if (!hasOwn(to, key)) {
- set(to, key, fromVal);
- } else if (isObject(toVal) && isObject(fromVal)) {
- mergeData(toVal, fromVal);
- }
- }
- return to
-}
-
-/**
- * Data
- */
-strats.data = function (
- parentVal,
- childVal,
- vm
-) {
- if (!vm) {
- // in a Vue.extend merge, both should be functions
- if (!childVal) {
- return parentVal
- }
- if (typeof childVal !== 'function') {
- "development" !== 'production' && warn(
- 'The "data" option should be a function ' +
- 'that returns a per-instance value in component ' +
- 'definitions.',
- vm
- );
- return parentVal
- }
- if (!parentVal) {
- return childVal
- }
- // when parentVal & childVal are both present,
- // we need to return a function that returns the
- // merged result of both functions... no need to
- // check if parentVal is a function here because
- // it has to be a function to pass previous merges.
- return function mergedDataFn () {
- return mergeData(
- childVal.call(this),
- parentVal.call(this)
- )
- }
- } else if (parentVal || childVal) {
- return function mergedInstanceDataFn () {
- // instance merge
- var instanceData = typeof childVal === 'function'
- ? childVal.call(vm)
- : childVal;
- var defaultData = typeof parentVal === 'function'
- ? parentVal.call(vm)
- : undefined;
- if (instanceData) {
- return mergeData(instanceData, defaultData)
- } else {
- return defaultData
- }
- }
- }
-};
-
-/**
- * Hooks and param attributes are merged as arrays.
- */
-function mergeHook (
- parentVal,
- childVal
-) {
- return childVal
- ? parentVal
- ? parentVal.concat(childVal)
- : Array.isArray(childVal)
- ? childVal
- : [childVal]
- : parentVal
-}
-
-config._lifecycleHooks.forEach(function (hook) {
- strats[hook] = mergeHook;
-});
-
-/**
- * Assets
- *
- * When a vm is present (instance creation), we need to do
- * a three-way merge between constructor options, instance
- * options and parent options.
- */
-function mergeAssets (parentVal, childVal) {
- var res = Object.create(parentVal || null);
- return childVal
- ? extend(res, childVal)
- : res
-}
-
-config._assetTypes.forEach(function (type) {
- strats[type + 's'] = mergeAssets;
-});
-
-/**
- * Watchers.
- *
- * Watchers hashes should not overwrite one
- * another, so we merge them as arrays.
- */
-strats.watch = function (parentVal, childVal) {
- /* istanbul ignore if */
- if (!childVal) { return parentVal }
- if (!parentVal) { return childVal }
- var ret = {};
- extend(ret, parentVal);
- for (var key in childVal) {
- var parent = ret[key];
- var child = childVal[key];
- if (parent && !Array.isArray(parent)) {
- parent = [parent];
- }
- ret[key] = parent
- ? parent.concat(child)
- : [child];
- }
- return ret
-};
-
-/**
- * Other object hashes.
- */
-strats.props =
-strats.methods =
-strats.computed = function (parentVal, childVal) {
- if (!childVal) { return parentVal }
- if (!parentVal) { return childVal }
- var ret = Object.create(null);
- extend(ret, parentVal);
- extend(ret, childVal);
- return ret
-};
-
-/**
- * Default strategy.
- */
-var defaultStrat = function (parentVal, childVal) {
- return childVal === undefined
- ? parentVal
- : childVal
-};
-
-/**
- * Make sure component options get converted to actual
- * constructors.
- */
-function normalizeComponents (options) {
- if (options.components) {
- var components = options.components;
- var def;
- for (var key in components) {
- var lower = key.toLowerCase();
- if (isBuiltInTag(lower) || config.isReservedTag(lower)) {
- "development" !== 'production' && warn(
- 'Do not use built-in or reserved HTML elements as component ' +
- 'id: ' + key
- );
- continue
- }
- def = components[key];
- if (isPlainObject(def)) {
- components[key] = Vue$3.extend(def);
- }
- }
- }
-}
-
-/**
- * Ensure all props option syntax are normalized into the
- * Object-based format.
- */
-function normalizeProps (options) {
- var props = options.props;
- if (!props) { return }
- var res = {};
- var i, val, name;
- if (Array.isArray(props)) {
- i = props.length;
- while (i--) {
- val = props[i];
- if (typeof val === 'string') {
- name = camelize(val);
- res[name] = { type: null };
- } else {
- warn('props must be strings when using array syntax.');
- }
- }
- } else if (isPlainObject(props)) {
- for (var key in props) {
- val = props[key];
- name = camelize(key);
- res[name] = isPlainObject(val)
- ? val
- : { type: val };
- }
- }
- options.props = res;
-}
-
-/**
- * Normalize raw function directives into object format.
- */
-function normalizeDirectives (options) {
- var dirs = options.directives;
- if (dirs) {
- for (var key in dirs) {
- var def = dirs[key];
- if (typeof def === 'function') {
- dirs[key] = { bind: def, update: def };
- }
- }
- }
-}
-
-/**
- * Merge two option objects into a new one.
- * Core utility used in both instantiation and inheritance.
- */
-function mergeOptions (
- parent,
- child,
- vm
-) {
- normalizeComponents(child);
- normalizeProps(child);
- normalizeDirectives(child);
- var extendsFrom = child.extends;
- if (extendsFrom) {
- parent = typeof extendsFrom === 'function'
- ? mergeOptions(parent, extendsFrom.options, vm)
- : mergeOptions(parent, extendsFrom, vm);
- }
- if (child.mixins) {
- for (var i = 0, l = child.mixins.length; i < l; i++) {
- var mixin = child.mixins[i];
- if (mixin.prototype instanceof Vue$3) {
- mixin = mixin.options;
- }
- parent = mergeOptions(parent, mixin, vm);
- }
- }
- var options = {};
- var key;
- for (key in parent) {
- mergeField(key);
- }
- for (key in child) {
- if (!hasOwn(parent, key)) {
- mergeField(key);
- }
- }
- function mergeField (key) {
- var strat = strats[key] || defaultStrat;
- options[key] = strat(parent[key], child[key], vm, key);
- }
- return options
-}
-
-/**
- * Resolve an asset.
- * This function is used because child instances need access
- * to assets defined in its ancestor chain.
- */
-function resolveAsset (
- options,
- type,
- id,
- warnMissing
-) {
- /* istanbul ignore if */
- if (typeof id !== 'string') {
- return
- }
- var assets = options[type];
- var res = assets[id] ||
- // camelCase ID
- assets[camelize(id)] ||
- // Pascal Case ID
- assets[capitalize(camelize(id))];
- if ("development" !== 'production' && warnMissing && !res) {
- warn(
- 'Failed to resolve ' + type.slice(0, -1) + ': ' + id,
- options
- );
- }
- return res
-}
-
-/* */
-
-function validateProp (
- key,
- propOptions,
- propsData,
- vm
-) {
- var prop = propOptions[key];
- var absent = !hasOwn(propsData, key);
- var value = propsData[key];
- // handle boolean props
- if (isBooleanType(prop.type)) {
- if (absent && !hasOwn(prop, 'default')) {
- value = false;
- } else if (value === '' || value === hyphenate(key)) {
- value = true;
- }
- }
- // check default value
- if (value === undefined) {
- value = getPropDefaultValue(vm, prop, key);
- // since the default value is a fresh copy,
- // make sure to observe it.
- var prevShouldConvert = observerState.shouldConvert;
- observerState.shouldConvert = true;
- observe(value);
- observerState.shouldConvert = prevShouldConvert;
- }
- {
- assertProp(prop, key, value, vm, absent);
- }
- return value
-}
-
-/**
- * Get the default value of a prop.
- */
-function getPropDefaultValue (vm, prop, name) {
- // no default, return undefined
- if (!hasOwn(prop, 'default')) {
- return undefined
- }
- var def = prop.default;
- // warn against non-factory defaults for Object & Array
- if (isObject(def)) {
- "development" !== 'production' && warn(
- 'Invalid default value for prop "' + name + '": ' +
- 'Props with type Object/Array must use a factory function ' +
- 'to return the default value.',
- vm
- );
- }
- // call factory function for non-Function types
- return typeof def === 'function' && prop.type !== Function
- ? def.call(vm)
- : def
-}
-
-/**
- * Assert whether a prop is valid.
- */
-function assertProp (
- prop,
- name,
- value,
- vm,
- absent
-) {
- if (prop.required && absent) {
- warn(
- 'Missing required prop: "' + name + '"',
- vm
- );
- return
- }
- if (value == null && !prop.required) {
- return
- }
- var type = prop.type;
- var valid = !type || type === true;
- var expectedTypes = [];
- if (type) {
- if (!Array.isArray(type)) {
- type = [type];
- }
- for (var i = 0; i < type.length && !valid; i++) {
- var assertedType = assertType(value, type[i]);
- expectedTypes.push(assertedType.expectedType);
- valid = assertedType.valid;
- }
- }
- if (!valid) {
- warn(
- 'Invalid prop: type check failed for prop "' + name + '".' +
- ' Expected ' + expectedTypes.map(capitalize).join(', ') +
- ', got ' + Object.prototype.toString.call(value).slice(8, -1) + '.',
- vm
- );
- return
- }
- var validator = prop.validator;
- if (validator) {
- if (!validator(value)) {
- warn(
- 'Invalid prop: custom validator check failed for prop "' + name + '".',
- vm
- );
- }
- }
-}
-
-/**
- * Assert the type of a value
- */
-function assertType (value, type) {
- var valid;
- var expectedType = getType(type);
- if (expectedType === 'String') {
- valid = typeof value === (expectedType = 'string');
- } else if (expectedType === 'Number') {
- valid = typeof value === (expectedType = 'number');
- } else if (expectedType === 'Boolean') {
- valid = typeof value === (expectedType = 'boolean');
- } else if (expectedType === 'Function') {
- valid = typeof value === (expectedType = 'function');
- } else if (expectedType === 'Object') {
- valid = isPlainObject(value);
- } else if (expectedType === 'Array') {
- valid = Array.isArray(value);
- } else {
- valid = value instanceof type;
- }
- return {
- valid: valid,
- expectedType: expectedType
- }
-}
-
-/**
- * Use function string name to check built-in types,
- * because a simple equality check will fail when running
- * across different vms / iframes.
- */
-function getType (fn) {
- var match = fn && fn.toString().match(/^\s*function (\w+)/);
- return match && match[1]
-}
-
-function isBooleanType (fn) {
- if (!Array.isArray(fn)) {
- return getType(fn) === 'Boolean'
- }
- for (var i = 0, len = fn.length; i < len; i++) {
- if (getType(fn[i]) === 'Boolean') {
- return true
- }
- }
- /* istanbul ignore next */
- return false
-}
-
-
-
-var util = Object.freeze({
- defineReactive: defineReactive$$1,
- _toString: _toString,
- toNumber: toNumber,
- makeMap: makeMap,
- isBuiltInTag: isBuiltInTag,
- remove: remove$1,
- hasOwn: hasOwn,
- isPrimitive: isPrimitive,
- cached: cached,
- camelize: camelize,
- capitalize: capitalize,
- hyphenate: hyphenate,
- bind: bind$1,
- toArray: toArray,
- extend: extend,
- isObject: isObject,
- isPlainObject: isPlainObject,
- toObject: toObject,
- noop: noop,
- no: no,
- genStaticKeys: genStaticKeys,
- looseEqual: looseEqual,
- looseIndexOf: looseIndexOf,
- isReserved: isReserved,
- def: def,
- parsePath: parsePath,
- hasProto: hasProto,
- inBrowser: inBrowser,
- UA: UA,
- isIE: isIE,
- isIE9: isIE9,
- isEdge: isEdge,
- isAndroid: isAndroid,
- isIOS: isIOS,
- devtools: devtools,
- nextTick: nextTick,
- get _Set () { return _Set; },
- mergeOptions: mergeOptions,
- resolveAsset: resolveAsset,
- get warn () { return warn; },
- get formatComponentName () { return formatComponentName; },
- validateProp: validateProp
-});
-
-/* */
-
-function initUse (Vue) {
- Vue.use = function (plugin) {
- /* istanbul ignore if */
- if (plugin.installed) {
- return
- }
- // additional parameters
- var args = toArray(arguments, 1);
- args.unshift(this);
- if (typeof plugin.install === 'function') {
- plugin.install.apply(plugin, args);
- } else {
- plugin.apply(null, args);
- }
- plugin.installed = true;
- return this
- };
-}
-
-/* */
-
-function initMixin$1 (Vue) {
- Vue.mixin = function (mixin) {
- Vue.options = mergeOptions(Vue.options, mixin);
- };
-}
-
-/* */
-
-function initExtend (Vue) {
- /**
- * Each instance constructor, including Vue, has a unique
- * cid. This enables us to create wrapped "child
- * constructors" for prototypal inheritance and cache them.
- */
- Vue.cid = 0;
- var cid = 1;
-
- /**
- * Class inheritance
- */
- Vue.extend = function (extendOptions) {
- extendOptions = extendOptions || {};
- var Super = this;
- var isFirstExtend = Super.cid === 0;
- if (isFirstExtend && extendOptions._Ctor) {
- return extendOptions._Ctor
- }
- var name = extendOptions.name || Super.options.name;
- {
- if (!/^[a-zA-Z][\w-]*$/.test(name)) {
- warn(
- 'Invalid component name: "' + name + '". Component names ' +
- 'can only contain alphanumeric characaters and the hyphen.'
- );
- name = null;
- }
- }
- var Sub = function VueComponent (options) {
- this._init(options);
- };
- Sub.prototype = Object.create(Super.prototype);
- Sub.prototype.constructor = Sub;
- Sub.cid = cid++;
- Sub.options = mergeOptions(
- Super.options,
- extendOptions
- );
- Sub['super'] = Super;
- // allow further extension
- Sub.extend = Super.extend;
- // create asset registers, so extended classes
- // can have their private assets too.
- config._assetTypes.forEach(function (type) {
- Sub[type] = Super[type];
- });
- // enable recursive self-lookup
- if (name) {
- Sub.options.components[name] = Sub;
- }
- // keep a reference to the super options at extension time.
- // later at instantiation we can check if Super's options have
- // been updated.
- Sub.superOptions = Super.options;
- Sub.extendOptions = extendOptions;
- // cache constructor
- if (isFirstExtend) {
- extendOptions._Ctor = Sub;
- }
- return Sub
- };
-}
-
-/* */
-
-function initAssetRegisters (Vue) {
- /**
- * Create asset registration methods.
- */
- config._assetTypes.forEach(function (type) {
- Vue[type] = function (
- id,
- definition
- ) {
- if (!definition) {
- return this.options[type + 's'][id]
- } else {
- /* istanbul ignore if */
- {
- if (type === 'component' && config.isReservedTag(id)) {
- warn(
- 'Do not use built-in or reserved HTML elements as component ' +
- 'id: ' + id
- );
- }
- }
- if (type === 'component' && isPlainObject(definition)) {
- definition.name = definition.name || id;
- definition = Vue.extend(definition);
- }
- if (type === 'directive' && typeof definition === 'function') {
- definition = { bind: definition, update: definition };
- }
- this.options[type + 's'][id] = definition;
- return definition
- }
- };
- });
-}
-
-var KeepAlive = {
- name: 'keep-alive',
- abstract: true,
- created: function created () {
- this.cache = Object.create(null);
- },
- render: function render () {
- var vnode = getFirstComponentChild(this.$slots.default);
- if (vnode && vnode.componentOptions) {
- var opts = vnode.componentOptions;
- var key = vnode.key == null
- // same constructor may get registered as different local components
- // so cid alone is not enough (#3269)
- ? opts.Ctor.cid + '::' + opts.tag
- : vnode.key;
- if (this.cache[key]) {
- vnode.child = this.cache[key].child;
- } else {
- this.cache[key] = vnode;
- }
- vnode.data.keepAlive = true;
- }
- return vnode
- },
- destroyed: function destroyed () {
- var this$1 = this;
-
- for (var key in this.cache) {
- var vnode = this$1.cache[key];
- callHook(vnode.child, 'deactivated');
- vnode.child.$destroy();
- }
- }
-};
-
-var builtInComponents = {
- KeepAlive: KeepAlive
-};
-
-/* */
-
-function initGlobalAPI (Vue) {
- // config
- var configDef = {};
- configDef.get = function () { return config; };
- {
- configDef.set = function () {
- warn(
- 'Do not replace the Vue.config object, set individual fields instead.'
- );
- };
- }
- Object.defineProperty(Vue, 'config', configDef);
- Vue.util = util;
- Vue.set = set;
- Vue.delete = del;
- Vue.nextTick = nextTick;
-
- Vue.options = Object.create(null);
- config._assetTypes.forEach(function (type) {
- Vue.options[type + 's'] = Object.create(null);
- });
-
- extend(Vue.options.components, builtInComponents);
-
- initUse(Vue);
- initMixin$1(Vue);
- initExtend(Vue);
- initAssetRegisters(Vue);
-}
-
-initGlobalAPI(Vue$3);
-
-Object.defineProperty(Vue$3.prototype, '$isServer', {
- get: function () { return config._isServer; }
-});
-
-Vue$3.version = '2.0.3';
-
-/* */
-
-// attributes that should be using props for binding
-var mustUseProp = makeMap('value,selected,checked,muted');
-
-var isEnumeratedAttr = makeMap('contenteditable,draggable,spellcheck');
-
-var isBooleanAttr = makeMap(
- 'allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,' +
- 'default,defaultchecked,defaultmuted,defaultselected,defer,disabled,' +
- 'enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,' +
- 'muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,' +
- 'required,reversed,scoped,seamless,selected,sortable,translate,' +
- 'truespeed,typemustmatch,visible'
-);
-
-var isAttr = makeMap(
- 'accept,accept-charset,accesskey,action,align,alt,async,autocomplete,' +
- 'autofocus,autoplay,autosave,bgcolor,border,buffered,challenge,charset,' +
- 'checked,cite,class,code,codebase,color,cols,colspan,content,http-equiv,' +
- 'name,contenteditable,contextmenu,controls,coords,data,datetime,default,' +
- 'defer,dir,dirname,disabled,download,draggable,dropzone,enctype,method,for,' +
- 'form,formaction,headers,<th>,height,hidden,high,href,hreflang,http-equiv,' +
- 'icon,id,ismap,itemprop,keytype,kind,label,lang,language,list,loop,low,' +
- 'manifest,max,maxlength,media,method,GET,POST,min,multiple,email,file,' +
- 'muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,' +
- 'preload,radiogroup,readonly,rel,required,reversed,rows,rowspan,sandbox,' +
- 'scope,scoped,seamless,selected,shape,size,type,text,password,sizes,span,' +
- 'spellcheck,src,srcdoc,srclang,srcset,start,step,style,summary,tabindex,' +
- 'target,title,type,usemap,value,width,wrap'
-);
-
-
-
-var xlinkNS = 'http://www.w3.org/1999/xlink';
-
-var isXlink = function (name) {
- return name.charAt(5) === ':' && name.slice(0, 5) === 'xlink'
-};
-
-var getXlinkProp = function (name) {
- return isXlink(name) ? name.slice(6, name.length) : ''
-};
-
-var isFalsyAttrValue = function (val) {
- return val == null || val === false
-};
-
-/* */
-
-function genClassForVnode (vnode) {
- var data = vnode.data;
- var parentNode = vnode;
- var childNode = vnode;
- while (childNode.child) {
- childNode = childNode.child._vnode;
- if (childNode.data) {
- data = mergeClassData(childNode.data, data);
- }
- }
- while ((parentNode = parentNode.parent)) {
- if (parentNode.data) {
- data = mergeClassData(data, parentNode.data);
- }
- }
- return genClassFromData(data)
-}
-
-function mergeClassData (child, parent) {
- return {
- staticClass: concat(child.staticClass, parent.staticClass),
- class: child.class
- ? [child.class, parent.class]
- : parent.class
- }
-}
-
-function genClassFromData (data) {
- var dynamicClass = data.class;
- var staticClass = data.staticClass;
- if (staticClass || dynamicClass) {
- return concat(staticClass, stringifyClass(dynamicClass))
- }
- /* istanbul ignore next */
- return ''
-}
-
-function concat (a, b) {
- return a ? b ? (a + ' ' + b) : a : (b || '')
-}
-
-function stringifyClass (value) {
- var res = '';
- if (!value) {
- return res
- }
- if (typeof value === 'string') {
- return value
- }
- if (Array.isArray(value)) {
- var stringified;
- for (var i = 0, l = value.length; i < l; i++) {
- if (value[i]) {
- if ((stringified = stringifyClass(value[i]))) {
- res += stringified + ' ';
- }
- }
- }
- return res.slice(0, -1)
- }
- if (isObject(value)) {
- for (var key in value) {
- if (value[key]) { res += key + ' '; }
- }
- return res.slice(0, -1)
- }
- /* istanbul ignore next */
- return res
-}
-
-/* */
-
-var namespaceMap = {
- svg: 'http://www.w3.org/2000/svg',
- math: 'http://www.w3.org/1998/Math/MathML'
-};
-
-var isHTMLTag = makeMap(
- 'html,body,base,head,link,meta,style,title,' +
- 'address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,' +
- 'div,dd,dl,dt,figcaption,figure,hr,img,li,main,ol,p,pre,ul,' +
- 'a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,' +
- 's,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,' +
- 'embed,object,param,source,canvas,script,noscript,del,ins,' +
- 'caption,col,colgroup,table,thead,tbody,td,th,tr,' +
- 'button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,' +
- 'output,progress,select,textarea,' +
- 'details,dialog,menu,menuitem,summary,' +
- 'content,element,shadow,template'
-);
-
-var isUnaryTag = makeMap(
- 'area,base,br,col,embed,frame,hr,img,input,isindex,keygen,' +
- 'link,meta,param,source,track,wbr',
- true
-);
-
-// Elements that you can, intentionally, leave open
-// (and which close themselves)
-var canBeLeftOpenTag = makeMap(
- 'colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source',
- true
-);
-
-// HTML5 tags https://html.spec.whatwg.org/multipage/indices.html#elements-3
-// Phrasing Content https://html.spec.whatwg.org/multipage/dom.html#phrasing-content
-var isNonPhrasingTag = makeMap(
- 'address,article,aside,base,blockquote,body,caption,col,colgroup,dd,' +
- 'details,dialog,div,dl,dt,fieldset,figcaption,figure,footer,form,' +
- 'h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,legend,li,menuitem,meta,' +
- 'optgroup,option,param,rp,rt,source,style,summary,tbody,td,tfoot,th,thead,' +
- 'title,tr,track',
- true
-);
-
-// this map is intentionally selective, only covering SVG elements that may
-// contain child elements.
-var isSVG = makeMap(
- 'svg,animate,circle,clippath,cursor,defs,desc,ellipse,filter,font,' +
- 'font-face,g,glyph,image,line,marker,mask,missing-glyph,path,pattern,' +
- 'polygon,polyline,rect,switch,symbol,text,textpath,tspan,use,view',
- true
-);
-
-var isPreTag = function (tag) { return tag === 'pre'; };
-
-var isReservedTag = function (tag) {
- return isHTMLTag(tag) || isSVG(tag)
-};
-
-function getTagNamespace (tag) {
- if (isSVG(tag)) {
- return 'svg'
- }
- // basic support for MathML
- // note it doesn't support other MathML elements being component roots
- if (tag === 'math') {
- return 'math'
- }
-}
-
-var unknownElementCache = Object.create(null);
-function isUnknownElement (tag) {
- /* istanbul ignore if */
- if (!inBrowser) {
- return true
- }
- if (isReservedTag(tag)) {
- return false
- }
- tag = tag.toLowerCase();
- /* istanbul ignore if */
- if (unknownElementCache[tag] != null) {
- return unknownElementCache[tag]
- }
- var el = document.createElement(tag);
- if (tag.indexOf('-') > -1) {
- // http://stackoverflow.com/a/28210364/1070244
- return (unknownElementCache[tag] = (
- el.constructor === window.HTMLUnknownElement ||
- el.constructor === window.HTMLElement
- ))
- } else {
- return (unknownElementCache[tag] = /HTMLUnknownElement/.test(el.toString()))
- }
-}
-
-/* */
-
-/**
- * Query an element selector if it's not an element already.
- */
-function query (el) {
- if (typeof el === 'string') {
- var selector = el;
- el = document.querySelector(el);
- if (!el) {
- "development" !== 'production' && warn(
- 'Cannot find element: ' + selector
- );
- return document.createElement('div')
- }
- }
- return el
-}
-
-/* */
-
-function createElement$1 (tagName, vnode) {
- var elm = document.createElement(tagName);
- if (tagName !== 'select') {
- return elm
- }
- if (vnode.data && vnode.data.attrs && 'multiple' in vnode.data.attrs) {
- elm.setAttribute('multiple', 'multiple');
- }
- return elm
-}
-
-function createElementNS (namespace, tagName) {
- return document.createElementNS(namespaceMap[namespace], tagName)
-}
-
-function createTextNode (text) {
- return document.createTextNode(text)
-}
-
-function createComment (text) {
- return document.createComment(text)
-}
-
-function insertBefore (parentNode, newNode, referenceNode) {
- parentNode.insertBefore(newNode, referenceNode);
-}
-
-function removeChild (node, child) {
- node.removeChild(child);
-}
-
-function appendChild (node, child) {
- node.appendChild(child);
-}
-
-function parentNode (node) {
- return node.parentNode
-}
-
-function nextSibling (node) {
- return node.nextSibling
-}
-
-function tagName (node) {
- return node.tagName
-}
-
-function setTextContent (node, text) {
- node.textContent = text;
-}
-
-function childNodes (node) {
- return node.childNodes
-}
-
-function setAttribute (node, key, val) {
- node.setAttribute(key, val);
-}
-
-
-var nodeOps = Object.freeze({
- createElement: createElement$1,
- createElementNS: createElementNS,
- createTextNode: createTextNode,
- createComment: createComment,
- insertBefore: insertBefore,
- removeChild: removeChild,
- appendChild: appendChild,
- parentNode: parentNode,
- nextSibling: nextSibling,
- tagName: tagName,
- setTextContent: setTextContent,
- childNodes: childNodes,
- setAttribute: setAttribute
-});
-
-/* */
-
-var ref = {
- create: function create (_, vnode) {
- registerRef(vnode);
- },
- update: function update (oldVnode, vnode) {
- if (oldVnode.data.ref !== vnode.data.ref) {
- registerRef(oldVnode, true);
- registerRef(vnode);
- }
- },
- destroy: function destroy (vnode) {
- registerRef(vnode, true);
- }
-};
-
-function registerRef (vnode, isRemoval) {
- var key = vnode.data.ref;
- if (!key) { return }
-
- var vm = vnode.context;
- var ref = vnode.child || vnode.elm;
- var refs = vm.$refs;
- if (isRemoval) {
- if (Array.isArray(refs[key])) {
- remove$1(refs[key], ref);
- } else if (refs[key] === ref) {
- refs[key] = undefined;
- }
- } else {
- if (vnode.data.refInFor) {
- if (Array.isArray(refs[key])) {
- refs[key].push(ref);
- } else {
- refs[key] = [ref];
- }
- } else {
- refs[key] = ref;
- }
- }
-}
-
-/**
- * Virtual DOM patching algorithm based on Snabbdom by
- * Simon Friis Vindum (@paldepind)
- * Licensed under the MIT License
- * https://github.com/paldepind/snabbdom/blob/master/LICENSE
- *
- * modified by Evan You (@yyx990803)
- *
-
-/*
- * Not type-checking this because this file is perf-critical and the cost
- * of making flow understand it is not worth it.
- */
-
-var emptyNode = new VNode('', {}, []);
-
-var hooks$1 = ['create', 'update', 'remove', 'destroy'];
-
-function isUndef (s) {
- return s == null
-}
-
-function isDef (s) {
- return s != null
-}
-
-function sameVnode (vnode1, vnode2) {
- return (
- vnode1.key === vnode2.key &&
- vnode1.tag === vnode2.tag &&
- vnode1.isComment === vnode2.isComment &&
- !vnode1.data === !vnode2.data
- )
-}
-
-function createKeyToOldIdx (children, beginIdx, endIdx) {
- var i, key;
- var map = {};
- for (i = beginIdx; i <= endIdx; ++i) {
- key = children[i].key;
- if (isDef(key)) { map[key] = i; }
- }
- return map
-}
-
-function createPatchFunction (backend) {
- var i, j;
- var cbs = {};
-
- var modules = backend.modules;
- var nodeOps = backend.nodeOps;
-
- for (i = 0; i < hooks$1.length; ++i) {
- cbs[hooks$1[i]] = [];
- for (j = 0; j < modules.length; ++j) {
- if (modules[j][hooks$1[i]] !== undefined) { cbs[hooks$1[i]].push(modules[j][hooks$1[i]]); }
- }
- }
-
- function emptyNodeAt (elm) {
- return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm)
- }
-
- function createRmCb (childElm, listeners) {
- function remove$$1 () {
- if (--remove$$1.listeners === 0) {
- removeElement(childElm);
- }
- }
- remove$$1.listeners = listeners;
- return remove$$1
- }
-
- function removeElement (el) {
- var parent = nodeOps.parentNode(el);
- nodeOps.removeChild(parent, el);
- }
-
- function createElm (vnode, insertedVnodeQueue, nested) {
- var i;
- var data = vnode.data;
- vnode.isRootInsert = !nested;
- if (isDef(data)) {
- if (isDef(i = data.hook) && isDef(i = i.init)) { i(vnode); }
- // after calling the init hook, if the vnode is a child component
- // it should've created a child instance and mounted it. the child
- // component also has set the placeholder vnode's elm.
- // in that case we can just return the element and be done.
- if (isDef(i = vnode.child)) {
- initComponent(vnode, insertedVnodeQueue);
- return vnode.elm
- }
- }
- var children = vnode.children;
- var tag = vnode.tag;
- if (isDef(tag)) {
- {
- if (
- !vnode.ns &&
- !(config.ignoredElements && config.ignoredElements.indexOf(tag) > -1) &&
- config.isUnknownElement(tag)
- ) {
- warn(
- 'Unknown custom element: <' + tag + '> - did you ' +
- 'register the component correctly? For recursive components, ' +
- 'make sure to provide the "name" option.',
- vnode.context
- );
- }
- }
- vnode.elm = vnode.ns
- ? nodeOps.createElementNS(vnode.ns, tag)
- : nodeOps.createElement(tag, vnode);
- setScope(vnode);
- createChildren(vnode, children, insertedVnodeQueue);
- if (isDef(data)) {
- invokeCreateHooks(vnode, insertedVnodeQueue);
- }
- } else if (vnode.isComment) {
- vnode.elm = nodeOps.createComment(vnode.text);
- } else {
- vnode.elm = nodeOps.createTextNode(vnode.text);
- }
- return vnode.elm
- }
-
- function createChildren (vnode, children, insertedVnodeQueue) {
- if (Array.isArray(children)) {
- for (var i = 0; i < children.length; ++i) {
- nodeOps.appendChild(vnode.elm, createElm(children[i], insertedVnodeQueue, true));
- }
- } else if (isPrimitive(vnode.text)) {
- nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(vnode.text));
- }
- }
-
- function isPatchable (vnode) {
- while (vnode.child) {
- vnode = vnode.child._vnode;
- }
- return isDef(vnode.tag)
- }
-
- function invokeCreateHooks (vnode, insertedVnodeQueue) {
- for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {
- cbs.create[i$1](emptyNode, vnode);
- }
- i = vnode.data.hook; // Reuse variable
- if (isDef(i)) {
- if (i.create) { i.create(emptyNode, vnode); }
- if (i.insert) { insertedVnodeQueue.push(vnode); }
- }
- }
-
- function initComponent (vnode, insertedVnodeQueue) {
- if (vnode.data.pendingInsert) {
- insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert);
- }
- vnode.elm = vnode.child.$el;
- if (isPatchable(vnode)) {
- invokeCreateHooks(vnode, insertedVnodeQueue);
- setScope(vnode);
- } else {
- // empty component root.
- // skip all element-related modules except for ref (#3455)
- registerRef(vnode);
- // make sure to invoke the insert hook
- insertedVnodeQueue.push(vnode);
- }
- }
-
- // set scope id attribute for scoped CSS.
- // this is implemented as a special case to avoid the overhead
- // of going through the normal attribute patching process.
- function setScope (vnode) {
- var i;
- if (isDef(i = vnode.context) && isDef(i = i.$options._scopeId)) {
- nodeOps.setAttribute(vnode.elm, i, '');
- }
- if (isDef(i = activeInstance) &&
- i !== vnode.context &&
- isDef(i = i.$options._scopeId)) {
- nodeOps.setAttribute(vnode.elm, i, '');
- }
- }
-
- function addVnodes (parentElm, before, vnodes, startIdx, endIdx, insertedVnodeQueue) {
- for (; startIdx <= endIdx; ++startIdx) {
- nodeOps.insertBefore(parentElm, createElm(vnodes[startIdx], insertedVnodeQueue), before);
- }
- }
-
- function invokeDestroyHook (vnode) {
- var i, j;
- var data = vnode.data;
- if (isDef(data)) {
- if (isDef(i = data.hook) && isDef(i = i.destroy)) { i(vnode); }
- for (i = 0; i < cbs.destroy.length; ++i) { cbs.destroy[i](vnode); }
- }
- if (isDef(i = vnode.children)) {
- for (j = 0; j < vnode.children.length; ++j) {
- invokeDestroyHook(vnode.children[j]);
- }
- }
- }
-
- function removeVnodes (parentElm, vnodes, startIdx, endIdx) {
- for (; startIdx <= endIdx; ++startIdx) {
- var ch = vnodes[startIdx];
- if (isDef(ch)) {
- if (isDef(ch.tag)) {
- removeAndInvokeRemoveHook(ch);
- invokeDestroyHook(ch);
- } else { // Text node
- nodeOps.removeChild(parentElm, ch.elm);
- }
- }
- }
- }
-
- function removeAndInvokeRemoveHook (vnode, rm) {
- if (rm || isDef(vnode.data)) {
- var listeners = cbs.remove.length + 1;
- if (!rm) {
- // directly removing
- rm = createRmCb(vnode.elm, listeners);
- } else {
- // we have a recursively passed down rm callback
- // increase the listeners count
- rm.listeners += listeners;
- }
- // recursively invoke hooks on child component root node
- if (isDef(i = vnode.child) && isDef(i = i._vnode) && isDef(i.data)) {
- removeAndInvokeRemoveHook(i, rm);
- }
- for (i = 0; i < cbs.remove.length; ++i) {
- cbs.remove[i](vnode, rm);
- }
- if (isDef(i = vnode.data.hook) && isDef(i = i.remove)) {
- i(vnode, rm);
- } else {
- rm();
- }
- } else {
- removeElement(vnode.elm);
- }
- }
-
- function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
- var oldStartIdx = 0;
- var newStartIdx = 0;
- var oldEndIdx = oldCh.length - 1;
- var oldStartVnode = oldCh[0];
- var oldEndVnode = oldCh[oldEndIdx];
- var newEndIdx = newCh.length - 1;
- var newStartVnode = newCh[0];
- var newEndVnode = newCh[newEndIdx];
- var oldKeyToIdx, idxInOld, elmToMove, before;
-
- // removeOnly is a special flag used only by <transition-group>
- // to ensure removed elements stay in correct relative positions
- // during leaving transitions
- var canMove = !removeOnly;
-
- while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
- if (isUndef(oldStartVnode)) {
- oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left
- } else if (isUndef(oldEndVnode)) {
- oldEndVnode = oldCh[--oldEndIdx];
- } else if (sameVnode(oldStartVnode, newStartVnode)) {
- patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue);
- oldStartVnode = oldCh[++oldStartIdx];
- newStartVnode = newCh[++newStartIdx];
- } else if (sameVnode(oldEndVnode, newEndVnode)) {
- patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue);
- oldEndVnode = oldCh[--oldEndIdx];
- newEndVnode = newCh[--newEndIdx];
- } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
- patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue);
- canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm));
- oldStartVnode = oldCh[++oldStartIdx];
- newEndVnode = newCh[--newEndIdx];
- } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
- patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);
- canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);
- oldEndVnode = oldCh[--oldEndIdx];
- newStartVnode = newCh[++newStartIdx];
- } else {
- if (isUndef(oldKeyToIdx)) { oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); }
- idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : null;
- if (isUndef(idxInOld)) { // New element
- nodeOps.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm);
- newStartVnode = newCh[++newStartIdx];
- } else {
- elmToMove = oldCh[idxInOld];
- /* istanbul ignore if */
- if ("development" !== 'production' && !elmToMove) {
- warn(
- 'It seems there are duplicate keys that is causing an update error. ' +
- 'Make sure each v-for item has a unique key.'
- );
- }
- if (elmToMove.tag !== newStartVnode.tag) {
- // same key but different element. treat as new element
- nodeOps.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm);
- newStartVnode = newCh[++newStartIdx];
- } else {
- patchVnode(elmToMove, newStartVnode, insertedVnodeQueue);
- oldCh[idxInOld] = undefined;
- canMove && nodeOps.insertBefore(parentElm, newStartVnode.elm, oldStartVnode.elm);
- newStartVnode = newCh[++newStartIdx];
- }
- }
- }
- }
- if (oldStartIdx > oldEndIdx) {
- before = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm;
- addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue);
- } else if (newStartIdx > newEndIdx) {
- removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);
- }
- }
-
- function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
- if (oldVnode === vnode) {
- return
- }
- // reuse element for static trees.
- // note we only do this if the vnode is cloned -
- // if the new node is not cloned it means the render functions have been
- // reset by the hot-reload-api and we need to do a proper re-render.
- if (vnode.isStatic &&
- oldVnode.isStatic &&
- vnode.key === oldVnode.key &&
- vnode.isCloned) {
- vnode.elm = oldVnode.elm;
- return
- }
- var i;
- var data = vnode.data;
- var hasData = isDef(data);
- if (hasData && isDef(i = data.hook) && isDef(i = i.prepatch)) {
- i(oldVnode, vnode);
- }
- var elm = vnode.elm = oldVnode.elm;
- var oldCh = oldVnode.children;
- var ch = vnode.children;
- if (hasData && isPatchable(vnode)) {
- for (i = 0; i < cbs.update.length; ++i) { cbs.update[i](oldVnode, vnode); }
- if (isDef(i = data.hook) && isDef(i = i.update)) { i(oldVnode, vnode); }
- }
- if (isUndef(vnode.text)) {
- if (isDef(oldCh) && isDef(ch)) {
- if (oldCh !== ch) { updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly); }
- } else if (isDef(ch)) {
- if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, ''); }
- addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
- } else if (isDef(oldCh)) {
- removeVnodes(elm, oldCh, 0, oldCh.length - 1);
- } else if (isDef(oldVnode.text)) {
- nodeOps.setTextContent(elm, '');
- }
- } else if (oldVnode.text !== vnode.text) {
- nodeOps.setTextContent(elm, vnode.text);
- }
- if (hasData) {
- if (isDef(i = data.hook) && isDef(i = i.postpatch)) { i(oldVnode, vnode); }
- }
- }
-
- function invokeInsertHook (vnode, queue, initial) {
- // delay insert hooks for component root nodes, invoke them after the
- // element is really inserted
- if (initial && vnode.parent) {
- vnode.parent.data.pendingInsert = queue;
- } else {
- for (var i = 0; i < queue.length; ++i) {
- queue[i].data.hook.insert(queue[i]);
- }
- }
- }
-
- var bailed = false;
- function hydrate (elm, vnode, insertedVnodeQueue) {
- {
- if (!assertNodeMatch(elm, vnode)) {
- return false
- }
- }
- vnode.elm = elm;
- var tag = vnode.tag;
- var data = vnode.data;
- var children = vnode.children;
- if (isDef(data)) {
- if (isDef(i = data.hook) && isDef(i = i.init)) { i(vnode, true /* hydrating */); }
- if (isDef(i = vnode.child)) {
- // child component. it should have hydrated its own tree.
- initComponent(vnode, insertedVnodeQueue);
- return true
- }
- }
- if (isDef(tag)) {
- if (isDef(children)) {
- var childNodes = nodeOps.childNodes(elm);
- // empty element, allow client to pick up and populate children
- if (!childNodes.length) {
- createChildren(vnode, children, insertedVnodeQueue);
- } else {
- var childrenMatch = true;
- if (childNodes.length !== children.length) {
- childrenMatch = false;
- } else {
- for (var i$1 = 0; i$1 < children.length; i$1++) {
- if (!hydrate(childNodes[i$1], children[i$1], insertedVnodeQueue)) {
- childrenMatch = false;
- break
- }
- }
- }
- if (!childrenMatch) {
- if ("development" !== 'production' &&
- typeof console !== 'undefined' &&
- !bailed) {
- bailed = true;
- console.warn('Parent: ', elm);
- console.warn('Mismatching childNodes vs. VNodes: ', childNodes, children);
- }
- return false
- }
- }
- }
- if (isDef(data)) {
- invokeCreateHooks(vnode, insertedVnodeQueue);
- }
- }
- return true
- }
-
- function assertNodeMatch (node, vnode) {
- if (vnode.tag) {
- return (
- vnode.tag.indexOf('vue-component') === 0 ||
- vnode.tag === nodeOps.tagName(node).toLowerCase()
- )
- } else {
- return _toString(vnode.text) === node.data
- }
- }
-
- return function patch (oldVnode, vnode, hydrating, removeOnly) {
- if (!vnode) {
- if (oldVnode) { invokeDestroyHook(oldVnode); }
- return
- }
-
- var elm, parent;
- var isInitialPatch = false;
- var insertedVnodeQueue = [];
-
- if (!oldVnode) {
- // empty mount, create new root element
- isInitialPatch = true;
- createElm(vnode, insertedVnodeQueue);
- } else {
- var isRealElement = isDef(oldVnode.nodeType);
- if (!isRealElement && sameVnode(oldVnode, vnode)) {
- patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly);
- } else {
- if (isRealElement) {
- // mounting to a real element
- // check if this is server-rendered content and if we can perform
- // a successful hydration.
- if (oldVnode.nodeType === 1 && oldVnode.hasAttribute('server-rendered')) {
- oldVnode.removeAttribute('server-rendered');
- hydrating = true;
- }
- if (hydrating) {
- if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
- invokeInsertHook(vnode, insertedVnodeQueue, true);
- return oldVnode
- } else {
- warn(
- 'The client-side rendered virtual DOM tree is not matching ' +
- 'server-rendered content. This is likely caused by incorrect ' +
- 'HTML markup, for example nesting block-level elements inside ' +
- '<p>, or missing <tbody>. Bailing hydration and performing ' +
- 'full client-side render.'
- );
- }
- }
- // either not server-rendered, or hydration failed.
- // create an empty node and replace it
- oldVnode = emptyNodeAt(oldVnode);
- }
- elm = oldVnode.elm;
- parent = nodeOps.parentNode(elm);
-
- createElm(vnode, insertedVnodeQueue);
-
- // component root element replaced.
- // update parent placeholder node element.
- if (vnode.parent) {
- vnode.parent.elm = vnode.elm;
- if (isPatchable(vnode)) {
- for (var i = 0; i < cbs.create.length; ++i) {
- cbs.create[i](emptyNode, vnode.parent);
- }
- }
- }
-
- if (parent !== null) {
- nodeOps.insertBefore(parent, vnode.elm, nodeOps.nextSibling(elm));
- removeVnodes(parent, [oldVnode], 0, 0);
- } else if (isDef(oldVnode.tag)) {
- invokeDestroyHook(oldVnode);
- }
- }
- }
-
- invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
- return vnode.elm
- }
-}
-
-/* */
-
-var directives = {
- create: updateDirectives,
- update: updateDirectives,
- destroy: function unbindDirectives (vnode) {
- updateDirectives(vnode, emptyNode);
- }
-};
-
-function updateDirectives (
- oldVnode,
- vnode
-) {
- if (!oldVnode.data.directives && !vnode.data.directives) {
- return
- }
- var isCreate = oldVnode === emptyNode;
- var oldDirs = normalizeDirectives$1(oldVnode.data.directives, oldVnode.context);
- var newDirs = normalizeDirectives$1(vnode.data.directives, vnode.context);
-
- var dirsWithInsert = [];
- var dirsWithPostpatch = [];
-
- var key, oldDir, dir;
- for (key in newDirs) {
- oldDir = oldDirs[key];
- dir = newDirs[key];
- if (!oldDir) {
- // new directive, bind
- callHook$1(dir, 'bind', vnode, oldVnode);
- if (dir.def && dir.def.inserted) {
- dirsWithInsert.push(dir);
- }
- } else {
- // existing directive, update
- dir.oldValue = oldDir.value;
- callHook$1(dir, 'update', vnode, oldVnode);
- if (dir.def && dir.def.componentUpdated) {
- dirsWithPostpatch.push(dir);
- }
- }
- }
-
- if (dirsWithInsert.length) {
- var callInsert = function () {
- dirsWithInsert.forEach(function (dir) {
- callHook$1(dir, 'inserted', vnode, oldVnode);
- });
- };
- if (isCreate) {
- mergeVNodeHook(vnode.data.hook || (vnode.data.hook = {}), 'insert', callInsert, 'dir-insert');
- } else {
- callInsert();
- }
- }
-
- if (dirsWithPostpatch.length) {
- mergeVNodeHook(vnode.data.hook || (vnode.data.hook = {}), 'postpatch', function () {
- dirsWithPostpatch.forEach(function (dir) {
- callHook$1(dir, 'componentUpdated', vnode, oldVnode);
- });
- }, 'dir-postpatch');
- }
-
- if (!isCreate) {
- for (key in oldDirs) {
- if (!newDirs[key]) {
- // no longer present, unbind
- callHook$1(oldDirs[key], 'unbind', oldVnode);
- }
- }
- }
-}
-
-var emptyModifiers = Object.create(null);
-
-function normalizeDirectives$1 (
- dirs,
- vm
-) {
- var res = Object.create(null);
- if (!dirs) {
- return res
- }
- var i, dir;
- for (i = 0; i < dirs.length; i++) {
- dir = dirs[i];
- if (!dir.modifiers) {
- dir.modifiers = emptyModifiers;
- }
- res[getRawDirName(dir)] = dir;
- dir.def = resolveAsset(vm.$options, 'directives', dir.name, true);
- }
- return res
-}
-
-function getRawDirName (dir) {
- return dir.rawName || ((dir.name) + "." + (Object.keys(dir.modifiers || {}).join('.')))
-}
-
-function callHook$1 (dir, hook, vnode, oldVnode) {
- var fn = dir.def && dir.def[hook];
- if (fn) {
- fn(vnode.elm, dir, vnode, oldVnode);
- }
-}
-
-var baseModules = [
- ref,
- directives
-];
-
-/* */
-
-function updateAttrs (oldVnode, vnode) {
- if (!oldVnode.data.attrs && !vnode.data.attrs) {
- return
- }
- var key, cur, old;
- var elm = vnode.elm;
- var oldAttrs = oldVnode.data.attrs || {};
- var attrs = vnode.data.attrs || {};
- // clone observed objects, as the user probably wants to mutate it
- if (attrs.__ob__) {
- attrs = vnode.data.attrs = extend({}, attrs);
- }
-
- for (key in attrs) {
- cur = attrs[key];
- old = oldAttrs[key];
- if (old !== cur) {
- setAttr(elm, key, cur);
- }
- }
- for (key in oldAttrs) {
- if (attrs[key] == null) {
- if (isXlink(key)) {
- elm.removeAttributeNS(xlinkNS, getXlinkProp(key));
- } else if (!isEnumeratedAttr(key)) {
- elm.removeAttribute(key);
- }
- }
- }
-}
-
-function setAttr (el, key, value) {
- if (isBooleanAttr(key)) {
- // set attribute for blank value
- // e.g. <option disabled>Select one</option>
- if (isFalsyAttrValue(value)) {
- el.removeAttribute(key);
- } else {
- el.setAttribute(key, key);
- }
- } else if (isEnumeratedAttr(key)) {
- el.setAttribute(key, isFalsyAttrValue(value) || value === 'false' ? 'false' : 'true');
- } else if (isXlink(key)) {
- if (isFalsyAttrValue(value)) {
- el.removeAttributeNS(xlinkNS, getXlinkProp(key));
- } else {
- el.setAttributeNS(xlinkNS, key, value);
- }
- } else {
- if (isFalsyAttrValue(value)) {
- el.removeAttribute(key);
- } else {
- el.setAttribute(key, value);
- }
- }
-}
-
-var attrs = {
- create: updateAttrs,
- update: updateAttrs
-};
-
-/* */
-
-function updateClass (oldVnode, vnode) {
- var el = vnode.elm;
- var data = vnode.data;
- var oldData = oldVnode.data;
- if (!data.staticClass && !data.class &&
- (!oldData || (!oldData.staticClass && !oldData.class))) {
- return
- }
-
- var cls = genClassForVnode(vnode);
-
- // handle transition classes
- var transitionClass = el._transitionClasses;
- if (transitionClass) {
- cls = concat(cls, stringifyClass(transitionClass));
- }
-
- // set the class
- if (cls !== el._prevClass) {
- el.setAttribute('class', cls);
- el._prevClass = cls;
- }
-}
-
-var klass = {
- create: updateClass,
- update: updateClass
-};
-
-// skip type checking this file because we need to attach private properties
-// to elements
-
-function updateDOMListeners (oldVnode, vnode) {
- if (!oldVnode.data.on && !vnode.data.on) {
- return
- }
- var on = vnode.data.on || {};
- var oldOn = oldVnode.data.on || {};
- var add = vnode.elm._v_add || (vnode.elm._v_add = function (event, handler, capture) {
- vnode.elm.addEventListener(event, handler, capture);
- });
- var remove = vnode.elm._v_remove || (vnode.elm._v_remove = function (event, handler) {
- vnode.elm.removeEventListener(event, handler);
- });
- updateListeners(on, oldOn, add, remove, vnode.context);
-}
-
-var events = {
- create: updateDOMListeners,
- update: updateDOMListeners
-};
-
-/* */
-
-function updateDOMProps (oldVnode, vnode) {
- if (!oldVnode.data.domProps && !vnode.data.domProps) {
- return
- }
- var key, cur;
- var elm = vnode.elm;
- var oldProps = oldVnode.data.domProps || {};
- var props = vnode.data.domProps || {};
- // clone observed objects, as the user probably wants to mutate it
- if (props.__ob__) {
- props = vnode.data.domProps = extend({}, props);
- }
-
- for (key in oldProps) {
- if (props[key] == null) {
- elm[key] = undefined;
- }
- }
- for (key in props) {
- // ignore children if the node has textContent or innerHTML,
- // as these will throw away existing DOM nodes and cause removal errors
- // on subsequent patches (#3360)
- if ((key === 'textContent' || key === 'innerHTML') && vnode.children) {
- vnode.children.length = 0;
- }
- cur = props[key];
- if (key === 'value') {
- // store value as _value as well since
- // non-string values will be stringified
- elm._value = cur;
- // avoid resetting cursor position when value is the same
- var strCur = cur == null ? '' : String(cur);
- if (elm.value !== strCur && !elm.composing) {
- elm.value = strCur;
- }
- } else {
- elm[key] = cur;
- }
- }
-}
-
-var domProps = {
- create: updateDOMProps,
- update: updateDOMProps
-};
-
-/* */
-
-var prefixes = ['Webkit', 'Moz', 'ms'];
-
-var testEl;
-var normalize = cached(function (prop) {
- testEl = testEl || document.createElement('div');
- prop = camelize(prop);
- if (prop !== 'filter' && (prop in testEl.style)) {
- return prop
- }
- var upper = prop.charAt(0).toUpperCase() + prop.slice(1);
- for (var i = 0; i < prefixes.length; i++) {
- var prefixed = prefixes[i] + upper;
- if (prefixed in testEl.style) {
- return prefixed
- }
- }
-});
-
-function updateStyle (oldVnode, vnode) {
- if ((!oldVnode.data || !oldVnode.data.style) && !vnode.data.style) {
- return
- }
- var cur, name;
- var el = vnode.elm;
- var oldStyle = oldVnode.data.style || {};
- var style = vnode.data.style || {};
-
- // handle string
- if (typeof style === 'string') {
- el.style.cssText = style;
- return
- }
-
- var needClone = style.__ob__;
-
- // handle array syntax
- if (Array.isArray(style)) {
- style = vnode.data.style = toObject(style);
- }
-
- // clone the style for future updates,
- // in case the user mutates the style object in-place.
- if (needClone) {
- style = vnode.data.style = extend({}, style);
- }
-
- for (name in oldStyle) {
- if (style[name] == null) {
- el.style[normalize(name)] = '';
- }
- }
- for (name in style) {
- cur = style[name];
- if (cur !== oldStyle[name]) {
- // ie9 setting to null has no effect, must use empty string
- el.style[normalize(name)] = cur == null ? '' : cur;
- }
- }
-}
-
-var style = {
- create: updateStyle,
- update: updateStyle
-};
-
-/* */
-
-/**
- * Add class with compatibility for SVG since classList is not supported on
- * SVG elements in IE
- */
-function addClass (el, cls) {
- /* istanbul ignore else */
- if (el.classList) {
- if (cls.indexOf(' ') > -1) {
- cls.split(/\s+/).forEach(function (c) { return el.classList.add(c); });
- } else {
- el.classList.add(cls);
- }
- } else {
- var cur = ' ' + el.getAttribute('class') + ' ';
- if (cur.indexOf(' ' + cls + ' ') < 0) {
- el.setAttribute('class', (cur + cls).trim());
- }
- }
-}
-
-/**
- * Remove class with compatibility for SVG since classList is not supported on
- * SVG elements in IE
- */
-function removeClass (el, cls) {
- /* istanbul ignore else */
- if (el.classList) {
- if (cls.indexOf(' ') > -1) {
- cls.split(/\s+/).forEach(function (c) { return el.classList.remove(c); });
- } else {
- el.classList.remove(cls);
- }
- } else {
- var cur = ' ' + el.getAttribute('class') + ' ';
- var tar = ' ' + cls + ' ';
- while (cur.indexOf(tar) >= 0) {
- cur = cur.replace(tar, ' ');
- }
- el.setAttribute('class', cur.trim());
- }
-}
-
-/* */
-
-var hasTransition = inBrowser && !isIE9;
-var TRANSITION = 'transition';
-var ANIMATION = 'animation';
-
-// Transition property/event sniffing
-var transitionProp = 'transition';
-var transitionEndEvent = 'transitionend';
-var animationProp = 'animation';
-var animationEndEvent = 'animationend';
-if (hasTransition) {
- /* istanbul ignore if */
- if (window.ontransitionend === undefined &&
- window.onwebkittransitionend !== undefined) {
- transitionProp = 'WebkitTransition';
- transitionEndEvent = 'webkitTransitionEnd';
- }
- if (window.onanimationend === undefined &&
- window.onwebkitanimationend !== undefined) {
- animationProp = 'WebkitAnimation';
- animationEndEvent = 'webkitAnimationEnd';
- }
-}
-
-var raf = (inBrowser && window.requestAnimationFrame) || setTimeout;
-function nextFrame (fn) {
- raf(function () {
- raf(fn);
- });
-}
-
-function addTransitionClass (el, cls) {
- (el._transitionClasses || (el._transitionClasses = [])).push(cls);
- addClass(el, cls);
-}
-
-function removeTransitionClass (el, cls) {
- if (el._transitionClasses) {
- remove$1(el._transitionClasses, cls);
- }
- removeClass(el, cls);
-}
-
-function whenTransitionEnds (
- el,
- expectedType,
- cb
-) {
- var ref = getTransitionInfo(el, expectedType);
- var type = ref.type;
- var timeout = ref.timeout;
- var propCount = ref.propCount;
- if (!type) { return cb() }
- var event = type === TRANSITION ? transitionEndEvent : animationEndEvent;
- var ended = 0;
- var end = function () {
- el.removeEventListener(event, onEnd);
- cb();
- };
- var onEnd = function (e) {
- if (e.target === el) {
- if (++ended >= propCount) {
- end();
- }
- }
- };
- setTimeout(function () {
- if (ended < propCount) {
- end();
- }
- }, timeout + 1);
- el.addEventListener(event, onEnd);
-}
-
-var transformRE = /\b(transform|all)(,|$)/;
-
-function getTransitionInfo (el, expectedType) {
- var styles = window.getComputedStyle(el);
- var transitioneDelays = styles[transitionProp + 'Delay'].split(', ');
- var transitionDurations = styles[transitionProp + 'Duration'].split(', ');
- var transitionTimeout = getTimeout(transitioneDelays, transitionDurations);
- var animationDelays = styles[animationProp + 'Delay'].split(', ');
- var animationDurations = styles[animationProp + 'Duration'].split(', ');
- var animationTimeout = getTimeout(animationDelays, animationDurations);
-
- var type;
- var timeout = 0;
- var propCount = 0;
- /* istanbul ignore if */
- if (expectedType === TRANSITION) {
- if (transitionTimeout > 0) {
- type = TRANSITION;
- timeout = transitionTimeout;
- propCount = transitionDurations.length;
- }
- } else if (expectedType === ANIMATION) {
- if (animationTimeout > 0) {
- type = ANIMATION;
- timeout = animationTimeout;
- propCount = animationDurations.length;
- }
- } else {
- timeout = Math.max(transitionTimeout, animationTimeout);
- type = timeout > 0
- ? transitionTimeout > animationTimeout
- ? TRANSITION
- : ANIMATION
- : null;
- propCount = type
- ? type === TRANSITION
- ? transitionDurations.length
- : animationDurations.length
- : 0;
- }
- var hasTransform =
- type === TRANSITION &&
- transformRE.test(styles[transitionProp + 'Property']);
- return {
- type: type,
- timeout: timeout,
- propCount: propCount,
- hasTransform: hasTransform
- }
-}
-
-function getTimeout (delays, durations) {
- return Math.max.apply(null, durations.map(function (d, i) {
- return toMs(d) + toMs(delays[i])
- }))
-}
-
-function toMs (s) {
- return Number(s.slice(0, -1)) * 1000
-}
-
-/* */
-
-function enter (vnode) {
- var el = vnode.elm;
-
- // call leave callback now
- if (el._leaveCb) {
- el._leaveCb.cancelled = true;
- el._leaveCb();
- }
-
- var data = resolveTransition(vnode.data.transition);
- if (!data) {
- return
- }
-
- /* istanbul ignore if */
- if (el._enterCb || el.nodeType !== 1) {
- return
- }
-
- var css = data.css;
- var type = data.type;
- var enterClass = data.enterClass;
- var enterActiveClass = data.enterActiveClass;
- var appearClass = data.appearClass;
- var appearActiveClass = data.appearActiveClass;
- var beforeEnter = data.beforeEnter;
- var enter = data.enter;
- var afterEnter = data.afterEnter;
- var enterCancelled = data.enterCancelled;
- var beforeAppear = data.beforeAppear;
- var appear = data.appear;
- var afterAppear = data.afterAppear;
- var appearCancelled = data.appearCancelled;
-
- // activeInstance will always be the <transition> component managing this
- // transition. One edge case to check is when the <transition> is placed
- // as the root node of a child component. In that case we need to check
- // <transition>'s parent for appear check.
- var transitionNode = activeInstance.$vnode;
- var context = transitionNode && transitionNode.parent
- ? transitionNode.parent.context
- : activeInstance;
-
- var isAppear = !context._isMounted || !vnode.isRootInsert;
-
- if (isAppear && !appear && appear !== '') {
- return
- }
-
- var startClass = isAppear ? appearClass : enterClass;
- var activeClass = isAppear ? appearActiveClass : enterActiveClass;
- var beforeEnterHook = isAppear ? (beforeAppear || beforeEnter) : beforeEnter;
- var enterHook = isAppear ? (typeof appear === 'function' ? appear : enter) : enter;
- var afterEnterHook = isAppear ? (afterAppear || afterEnter) : afterEnter;
- var enterCancelledHook = isAppear ? (appearCancelled || enterCancelled) : enterCancelled;
-
- var expectsCSS = css !== false && !isIE9;
- var userWantsControl =
- enterHook &&
- // enterHook may be a bound method which exposes
- // the length of original fn as _length
- (enterHook._length || enterHook.length) > 1;
-
- var cb = el._enterCb = once(function () {
- if (expectsCSS) {
- removeTransitionClass(el, activeClass);
- }
- if (cb.cancelled) {
- if (expectsCSS) {
- removeTransitionClass(el, startClass);
- }
- enterCancelledHook && enterCancelledHook(el);
- } else {
- afterEnterHook && afterEnterHook(el);
- }
- el._enterCb = null;
- });
-
- if (!vnode.data.show) {
- // remove pending leave element on enter by injecting an insert hook
- mergeVNodeHook(vnode.data.hook || (vnode.data.hook = {}), 'insert', function () {
- var parent = el.parentNode;
- var pendingNode = parent && parent._pending && parent._pending[vnode.key];
- if (pendingNode && pendingNode.tag === vnode.tag && pendingNode.elm._leaveCb) {
- pendingNode.elm._leaveCb();
- }
- enterHook && enterHook(el, cb);
- }, 'transition-insert');
- }
-
- // start enter transition
- beforeEnterHook && beforeEnterHook(el);
- if (expectsCSS) {
- addTransitionClass(el, startClass);
- addTransitionClass(el, activeClass);
- nextFrame(function () {
- removeTransitionClass(el, startClass);
- if (!cb.cancelled && !userWantsControl) {
- whenTransitionEnds(el, type, cb);
- }
- });
- }
-
- if (vnode.data.show) {
- enterHook && enterHook(el, cb);
- }
-
- if (!expectsCSS && !userWantsControl) {
- cb();
- }
-}
-
-function leave (vnode, rm) {
- var el = vnode.elm;
-
- // call enter callback now
- if (el._enterCb) {
- el._enterCb.cancelled = true;
- el._enterCb();
- }
-
- var data = resolveTransition(vnode.data.transition);
- if (!data) {
- return rm()
- }
-
- /* istanbul ignore if */
- if (el._leaveCb || el.nodeType !== 1) {
- return
- }
-
- var css = data.css;
- var type = data.type;
- var leaveClass = data.leaveClass;
- var leaveActiveClass = data.leaveActiveClass;
- var beforeLeave = data.beforeLeave;
- var leave = data.leave;
- var afterLeave = data.afterLeave;
- var leaveCancelled = data.leaveCancelled;
- var delayLeave = data.delayLeave;
-
- var expectsCSS = css !== false && !isIE9;
- var userWantsControl =
- leave &&
- // leave hook may be a bound method which exposes
- // the length of original fn as _length
- (leave._length || leave.length) > 1;
-
- var cb = el._leaveCb = once(function () {
- if (el.parentNode && el.parentNode._pending) {
- el.parentNode._pending[vnode.key] = null;
- }
- if (expectsCSS) {
- removeTransitionClass(el, leaveActiveClass);
- }
- if (cb.cancelled) {
- if (expectsCSS) {
- removeTransitionClass(el, leaveClass);
- }
- leaveCancelled && leaveCancelled(el);
- } else {
- rm();
- afterLeave && afterLeave(el);
- }
- el._leaveCb = null;
- });
-
- if (delayLeave) {
- delayLeave(performLeave);
- } else {
- performLeave();
- }
-
- function performLeave () {
- // the delayed leave may have already been cancelled
- if (cb.cancelled) {
- return
- }
- // record leaving element
- if (!vnode.data.show) {
- (el.parentNode._pending || (el.parentNode._pending = {}))[vnode.key] = vnode;
- }
- beforeLeave && beforeLeave(el);
- if (expectsCSS) {
- addTransitionClass(el, leaveClass);
- addTransitionClass(el, leaveActiveClass);
- nextFrame(function () {
- removeTransitionClass(el, leaveClass);
- if (!cb.cancelled && !userWantsControl) {
- whenTransitionEnds(el, type, cb);
- }
- });
- }
- leave && leave(el, cb);
- if (!expectsCSS && !userWantsControl) {
- cb();
- }
- }
-}
-
-function resolveTransition (def$$1) {
- if (!def$$1) {
- return
- }
- /* istanbul ignore else */
- if (typeof def$$1 === 'object') {
- var res = {};
- if (def$$1.css !== false) {
- extend(res, autoCssTransition(def$$1.name || 'v'));
- }
- extend(res, def$$1);
- return res
- } else if (typeof def$$1 === 'string') {
- return autoCssTransition(def$$1)
- }
-}
-
-var autoCssTransition = cached(function (name) {
- return {
- enterClass: (name + "-enter"),
- leaveClass: (name + "-leave"),
- appearClass: (name + "-enter"),
- enterActiveClass: (name + "-enter-active"),
- leaveActiveClass: (name + "-leave-active"),
- appearActiveClass: (name + "-enter-active")
- }
-});
-
-function once (fn) {
- var called = false;
- return function () {
- if (!called) {
- called = true;
- fn();
- }
- }
-}
-
-var transition = inBrowser ? {
- create: function create (_, vnode) {
- if (!vnode.data.show) {
- enter(vnode);
- }
- },
- remove: function remove (vnode, rm) {
- /* istanbul ignore else */
- if (!vnode.data.show) {
- leave(vnode, rm);
- } else {
- rm();
- }
- }
-} : {};
-
-var platformModules = [
- attrs,
- klass,
- events,
- domProps,
- style,
- transition
-];
-
-/* */
-
-// the directive module should be applied last, after all
-// built-in modules have been applied.
-var modules = platformModules.concat(baseModules);
-
-var patch$1 = createPatchFunction({ nodeOps: nodeOps, modules: modules });
-
-/**
- * Not type checking this file because flow doesn't like attaching
- * properties to Elements.
- */
-
-var modelableTagRE = /^input|select|textarea|vue-component-[0-9]+(-[0-9a-zA-Z_\-]*)?$/;
-
-/* istanbul ignore if */
-if (isIE9) {
- // http://www.matts411.com/post/internet-explorer-9-oninput/
- document.addEventListener('selectionchange', function () {
- var el = document.activeElement;
- if (el && el.vmodel) {
- trigger(el, 'input');
- }
- });
-}
-
-var model = {
- inserted: function inserted (el, binding, vnode) {
- {
- if (!modelableTagRE.test(vnode.tag)) {
- warn(
- "v-model is not supported on element type: <" + (vnode.tag) + ">. " +
- 'If you are working with contenteditable, it\'s recommended to ' +
- 'wrap a library dedicated for that purpose inside a custom component.',
- vnode.context
- );
- }
- }
- if (vnode.tag === 'select') {
- var cb = function () {
- setSelected(el, binding, vnode.context);
- };
- cb();
- /* istanbul ignore if */
- if (isIE || isEdge) {
- setTimeout(cb, 0);
- }
- } else if (
- (vnode.tag === 'textarea' || el.type === 'text') &&
- !binding.modifiers.lazy
- ) {
- if (!isAndroid) {
- el.addEventListener('compositionstart', onCompositionStart);
- el.addEventListener('compositionend', onCompositionEnd);
- }
- /* istanbul ignore if */
- if (isIE9) {
- el.vmodel = true;
- }
- }
- },
- componentUpdated: function componentUpdated (el, binding, vnode) {
- if (vnode.tag === 'select') {
- setSelected(el, binding, vnode.context);
- // in case the options rendered by v-for have changed,
- // it's possible that the value is out-of-sync with the rendered options.
- // detect such cases and filter out values that no longer has a matchig
- // option in the DOM.
- var needReset = el.multiple
- ? binding.value.some(function (v) { return hasNoMatchingOption(v, el.options); })
- : binding.value !== binding.oldValue && hasNoMatchingOption(binding.value, el.options);
- if (needReset) {
- trigger(el, 'change');
- }
- }
- }
-};
-
-function setSelected (el, binding, vm) {
- var value = binding.value;
- var isMultiple = el.multiple;
- if (isMultiple && !Array.isArray(value)) {
- "development" !== 'production' && warn(
- "<select multiple v-model=\"" + (binding.expression) + "\"> " +
- "expects an Array value for its binding, but got " + (Object.prototype.toString.call(value).slice(8, -1)),
- vm
- );
- return
- }
- var selected, option;
- for (var i = 0, l = el.options.length; i < l; i++) {
- option = el.options[i];
- if (isMultiple) {
- selected = looseIndexOf(value, getValue(option)) > -1;
- if (option.selected !== selected) {
- option.selected = selected;
- }
- } else {
- if (looseEqual(getValue(option), value)) {
- if (el.selectedIndex !== i) {
- el.selectedIndex = i;
- }
- return
- }
- }
- }
- if (!isMultiple) {
- el.selectedIndex = -1;
- }
-}
-
-function hasNoMatchingOption (value, options) {
- for (var i = 0, l = options.length; i < l; i++) {
- if (looseEqual(getValue(options[i]), value)) {
- return false
- }
- }
- return true
-}
-
-function getValue (option) {
- return '_value' in option
- ? option._value
- : option.value
-}
-
-function onCompositionStart (e) {
- e.target.composing = true;
-}
-
-function onCompositionEnd (e) {
- e.target.composing = false;
- trigger(e.target, 'input');
-}
-
-function trigger (el, type) {
- var e = document.createEvent('HTMLEvents');
- e.initEvent(type, true, true);
- el.dispatchEvent(e);
-}
-
-/* */
-
-// recursively search for possible transition defined inside the component root
-function locateNode (vnode) {
- return vnode.child && (!vnode.data || !vnode.data.transition)
- ? locateNode(vnode.child._vnode)
- : vnode
-}
-
-var show = {
- bind: function bind (el, ref, vnode) {
- var value = ref.value;
-
- vnode = locateNode(vnode);
- var transition = vnode.data && vnode.data.transition;
- if (value && transition && !isIE9) {
- enter(vnode);
- }
- var originalDisplay = el.style.display === 'none' ? '' : el.style.display;
- el.style.display = value ? originalDisplay : 'none';
- el.__vOriginalDisplay = originalDisplay;
- },
- update: function update (el, ref, vnode) {
- var value = ref.value;
- var oldValue = ref.oldValue;
-
- /* istanbul ignore if */
- if (value === oldValue) { return }
- vnode = locateNode(vnode);
- var transition = vnode.data && vnode.data.transition;
- if (transition && !isIE9) {
- if (value) {
- enter(vnode);
- el.style.display = el.__vOriginalDisplay;
- } else {
- leave(vnode, function () {
- el.style.display = 'none';
- });
- }
- } else {
- el.style.display = value ? el.__vOriginalDisplay : 'none';
- }
- }
-};
-
-var platformDirectives = {
- model: model,
- show: show
-};
-
-/* */
-
-// Provides transition support for a single element/component.
-// supports transition mode (out-in / in-out)
-
-var transitionProps = {
- name: String,
- appear: Boolean,
- css: Boolean,
- mode: String,
- type: String,
- enterClass: String,
- leaveClass: String,
- enterActiveClass: String,
- leaveActiveClass: String,
- appearClass: String,
- appearActiveClass: String
-};
-
-// in case the child is also an abstract component, e.g. <keep-alive>
-// we want to recrusively retrieve the real component to be rendered
-function getRealChild (vnode) {
- var compOptions = vnode && vnode.componentOptions;
- if (compOptions && compOptions.Ctor.options.abstract) {
- return getRealChild(getFirstComponentChild(compOptions.children))
- } else {
- return vnode
- }
-}
-
-function extractTransitionData (comp) {
- var data = {};
- var options = comp.$options;
- // props
- for (var key in options.propsData) {
- data[key] = comp[key];
- }
- // events.
- // extract listeners and pass them directly to the transition methods
- var listeners = options._parentListeners;
- for (var key$1 in listeners) {
- data[camelize(key$1)] = listeners[key$1].fn;
- }
- return data
-}
-
-function placeholder (h, rawChild) {
- return /\d-keep-alive$/.test(rawChild.tag)
- ? h('keep-alive')
- : null
-}
-
-function hasParentTransition (vnode) {
- while ((vnode = vnode.parent)) {
- if (vnode.data.transition) {
- return true
- }
- }
-}
-
-var Transition = {
- name: 'transition',
- props: transitionProps,
- abstract: true,
- render: function render (h) {
- var this$1 = this;
-
- var children = this.$slots.default;
- if (!children) {
- return
- }
-
- // filter out text nodes (possible whitespaces)
- children = children.filter(function (c) { return c.tag; });
- /* istanbul ignore if */
- if (!children.length) {
- return
- }
-
- // warn multiple elements
- if ("development" !== 'production' && children.length > 1) {
- warn(
- '<transition> can only be used on a single element. Use ' +
- '<transition-group> for lists.',
- this.$parent
- );
- }
-
- var mode = this.mode;
-
- // warn invalid mode
- if ("development" !== 'production' &&
- mode && mode !== 'in-out' && mode !== 'out-in') {
- warn(
- 'invalid <transition> mode: ' + mode,
- this.$parent
- );
- }
-
- var rawChild = children[0];
-
- // if this is a component root node and the component's
- // parent container node also has transition, skip.
- if (hasParentTransition(this.$vnode)) {
- return rawChild
- }
-
- // apply transition data to child
- // use getRealChild() to ignore abstract components e.g. keep-alive
- var child = getRealChild(rawChild);
- /* istanbul ignore if */
- if (!child) {
- return rawChild
- }
-
- if (this._leaving) {
- return placeholder(h, rawChild)
- }
-
- var key = child.key = child.key == null || child.isStatic
- ? ("__v" + (child.tag + this._uid) + "__")
- : child.key;
- var data = (child.data || (child.data = {})).transition = extractTransitionData(this);
- var oldRawChild = this._vnode;
- var oldChild = getRealChild(oldRawChild);
-
- // mark v-show
- // so that the transition module can hand over the control to the directive
- if (child.data.directives && child.data.directives.some(function (d) { return d.name === 'show'; })) {
- child.data.show = true;
- }
-
- if (oldChild && oldChild.data && oldChild.key !== key) {
- // replace old child transition data with fresh one
- // important for dynamic transitions!
- var oldData = oldChild.data.transition = extend({}, data);
-
- // handle transition mode
- if (mode === 'out-in') {
- // return placeholder node and queue update when leave finishes
- this._leaving = true;
- mergeVNodeHook(oldData, 'afterLeave', function () {
- this$1._leaving = false;
- this$1.$forceUpdate();
- }, key);
- return placeholder(h, rawChild)
- } else if (mode === 'in-out') {
- var delayedLeave;
- var performLeave = function () { delayedLeave(); };
- mergeVNodeHook(data, 'afterEnter', performLeave, key);
- mergeVNodeHook(data, 'enterCancelled', performLeave, key);
- mergeVNodeHook(oldData, 'delayLeave', function (leave) {
- delayedLeave = leave;
- }, key);
- }
- }
-
- return rawChild
- }
-};
-
-/* */
-
-// Provides transition support for list items.
-// supports move transitions using the FLIP technique.
-
-// Because the vdom's children update algorithm is "unstable" - i.e.
-// it doesn't guarantee the relative positioning of removed elements,
-// we force transition-group to update its children into two passes:
-// in the first pass, we remove all nodes that need to be removed,
-// triggering their leaving transition; in the second pass, we insert/move
-// into the final disired state. This way in the second pass removed
-// nodes will remain where they should be.
-
-var props = extend({
- tag: String,
- moveClass: String
-}, transitionProps);
-
-delete props.mode;
-
-var TransitionGroup = {
- props: props,
-
- render: function render (h) {
- var tag = this.tag || this.$vnode.data.tag || 'span';
- var map = Object.create(null);
- var prevChildren = this.prevChildren = this.children;
- var rawChildren = this.$slots.default || [];
- var children = this.children = [];
- var transitionData = extractTransitionData(this);
-
- for (var i = 0; i < rawChildren.length; i++) {
- var c = rawChildren[i];
- if (c.tag) {
- if (c.key != null && String(c.key).indexOf('__vlist') !== 0) {
- children.push(c);
- map[c.key] = c
- ;(c.data || (c.data = {})).transition = transitionData;
- } else {
- var opts = c.componentOptions;
- var name = opts
- ? (opts.Ctor.options.name || opts.tag)
- : c.tag;
- warn(("<transition-group> children must be keyed: <" + name + ">"));
- }
- }
- }
-
- if (prevChildren) {
- var kept = [];
- var removed = [];
- for (var i$1 = 0; i$1 < prevChildren.length; i$1++) {
- var c$1 = prevChildren[i$1];
- c$1.data.transition = transitionData;
- c$1.data.pos = c$1.elm.getBoundingClientRect();
- if (map[c$1.key]) {
- kept.push(c$1);
- } else {
- removed.push(c$1);
- }
- }
- this.kept = h(tag, null, kept);
- this.removed = removed;
- }
-
- return h(tag, null, children)
- },
-
- beforeUpdate: function beforeUpdate () {
- // force removing pass
- this.__patch__(
- this._vnode,
- this.kept,
- false, // hydrating
- true // removeOnly (!important, avoids unnecessary moves)
- );
- this._vnode = this.kept;
- },
-
- updated: function updated () {
- var children = this.prevChildren;
- var moveClass = this.moveClass || (this.name + '-move');
- if (!children.length || !this.hasMove(children[0].elm, moveClass)) {
- return
- }
-
- // we divide the work into three loops to avoid mixing DOM reads and writes
- // in each iteration - which helps prevent layout thrashing.
- children.forEach(callPendingCbs);
- children.forEach(recordPosition);
- children.forEach(applyTranslation);
-
- // force reflow to put everything in position
- var f = document.body.offsetHeight; // eslint-disable-line
-
- children.forEach(function (c) {
- if (c.data.moved) {
- var el = c.elm;
- var s = el.style;
- addTransitionClass(el, moveClass);
- s.transform = s.WebkitTransform = s.transitionDuration = '';
- el.addEventListener(transitionEndEvent, el._moveCb = function cb (e) {
- if (!e || /transform$/.test(e.propertyName)) {
- el.removeEventListener(transitionEndEvent, cb);
- el._moveCb = null;
- removeTransitionClass(el, moveClass);
- }
- });
- }
- });
- },
-
- methods: {
- hasMove: function hasMove (el, moveClass) {
- /* istanbul ignore if */
- if (!hasTransition) {
- return false
- }
- if (this._hasMove != null) {
- return this._hasMove
- }
- addTransitionClass(el, moveClass);
- var info = getTransitionInfo(el);
- removeTransitionClass(el, moveClass);
- return (this._hasMove = info.hasTransform)
- }
- }
-};
-
-function callPendingCbs (c) {
- /* istanbul ignore if */
- if (c.elm._moveCb) {
- c.elm._moveCb();
- }
- /* istanbul ignore if */
- if (c.elm._enterCb) {
- c.elm._enterCb();
- }
-}
-
-function recordPosition (c) {
- c.data.newPos = c.elm.getBoundingClientRect();
-}
-
-function applyTranslation (c) {
- var oldPos = c.data.pos;
- var newPos = c.data.newPos;
- var dx = oldPos.left - newPos.left;
- var dy = oldPos.top - newPos.top;
- if (dx || dy) {
- c.data.moved = true;
- var s = c.elm.style;
- s.transform = s.WebkitTransform = "translate(" + dx + "px," + dy + "px)";
- s.transitionDuration = '0s';
- }
-}
-
-var platformComponents = {
- Transition: Transition,
- TransitionGroup: TransitionGroup
-};
-
-/* */
-
-// install platform specific utils
-Vue$3.config.isUnknownElement = isUnknownElement;
-Vue$3.config.isReservedTag = isReservedTag;
-Vue$3.config.getTagNamespace = getTagNamespace;
-Vue$3.config.mustUseProp = mustUseProp;
-
-// install platform runtime directives & components
-extend(Vue$3.options.directives, platformDirectives);
-extend(Vue$3.options.components, platformComponents);
-
-// install platform patch function
-Vue$3.prototype.__patch__ = config._isServer ? noop : patch$1;
-
-// wrap mount
-Vue$3.prototype.$mount = function (
- el,
- hydrating
-) {
- el = el && !config._isServer ? query(el) : undefined;
- return this._mount(el, hydrating)
-};
-
-// devtools global hook
-/* istanbul ignore next */
-setTimeout(function () {
- if (config.devtools) {
- if (devtools) {
- devtools.emit('init', Vue$3);
- } else if (
- "development" !== 'production' &&
- inBrowser && /Chrome\/\d+/.test(window.navigator.userAgent)
- ) {
- console.log(
- 'Download the Vue Devtools for a better development experience:\n' +
- 'https://github.com/vuejs/vue-devtools'
- );
- }
- }
-}, 0);
-
-/* */
-
-// check whether current browser encodes a char inside attribute values
-function shouldDecode (content, encoded) {
- var div = document.createElement('div');
- div.innerHTML = "<div a=\"" + content + "\">";
- return div.innerHTML.indexOf(encoded) > 0
-}
-
-// #3663
-// IE encodes newlines inside attribute values while other browsers don't
-var shouldDecodeNewlines = inBrowser ? shouldDecode('\n', '&#10;') : false;
-
-/* */
-
-var decoder = document.createElement('div');
-
-function decode (html) {
- decoder.innerHTML = html;
- return decoder.textContent
-}
-
-/**
- * Not type-checking this file because it's mostly vendor code.
- */
-
-/*!
- * HTML Parser By John Resig (ejohn.org)
- * Modified by Juriy "kangax" Zaytsev
- * Original code by Erik Arvidsson, Mozilla Public License
- * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
- */
-
-// Regular Expressions for parsing tags and attributes
-var singleAttrIdentifier = /([^\s"'<>\/=]+)/;
-var singleAttrAssign = /(?:=)/;
-var singleAttrValues = [
- // attr value double quotes
- /"([^"]*)"+/.source,
- // attr value, single quotes
- /'([^']*)'+/.source,
- // attr value, no quotes
- /([^\s"'=<>`]+)/.source
-];
-var attribute = new RegExp(
- '^\\s*' + singleAttrIdentifier.source +
- '(?:\\s*(' + singleAttrAssign.source + ')' +
- '\\s*(?:' + singleAttrValues.join('|') + '))?'
-);
-
-// could use https://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-QName
-// but for Vue templates we can enforce a simple charset
-var ncname = '[a-zA-Z_][\\w\\-\\.]*';
-var qnameCapture = '((?:' + ncname + '\\:)?' + ncname + ')';
-var startTagOpen = new RegExp('^<' + qnameCapture);
-var startTagClose = /^\s*(\/?)>/;
-var endTag = new RegExp('^<\\/' + qnameCapture + '[^>]*>');
-var doctype = /^<!DOCTYPE [^>]+>/i;
-
-var IS_REGEX_CAPTURING_BROKEN = false;
-'x'.replace(/x(.)?/g, function (m, g) {
- IS_REGEX_CAPTURING_BROKEN = g === '';
-});
-
-// Special Elements (can contain anything)
-var isSpecialTag = makeMap('script,style', true);
-
-var reCache = {};
-
-var ltRE = /&lt;/g;
-var gtRE = /&gt;/g;
-var nlRE = /&#10;/g;
-var ampRE = /&amp;/g;
-var quoteRE = /&quot;/g;
-
-function decodeAttr (value, shouldDecodeNewlines) {
- if (shouldDecodeNewlines) {
- value = value.replace(nlRE, '\n');
- }
- return value
- .replace(ltRE, '<')
- .replace(gtRE, '>')
- .replace(ampRE, '&')
- .replace(quoteRE, '"')
-}
-
-function parseHTML (html, options) {
- var stack = [];
- var expectHTML = options.expectHTML;
- var isUnaryTag$$1 = options.isUnaryTag || no;
- var index = 0;
- var last, lastTag;
- while (html) {
- last = html;
- // Make sure we're not in a script or style element
- if (!lastTag || !isSpecialTag(lastTag)) {
- var textEnd = html.indexOf('<');
- if (textEnd === 0) {
- // Comment:
- if (/^<!--/.test(html)) {
- var commentEnd = html.indexOf('-->');
-
- if (commentEnd >= 0) {
- advance(commentEnd + 3);
- continue
- }
- }
-
- // http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment
- if (/^<!\[/.test(html)) {
- var conditionalEnd = html.indexOf(']>');
-
- if (conditionalEnd >= 0) {
- advance(conditionalEnd + 2);
- continue
- }
- }
-
- // Doctype:
- var doctypeMatch = html.match(doctype);
- if (doctypeMatch) {
- advance(doctypeMatch[0].length);
- continue
- }
-
- // End tag:
- var endTagMatch = html.match(endTag);
- if (endTagMatch) {
- var curIndex = index;
- advance(endTagMatch[0].length);
- parseEndTag(endTagMatch[0], endTagMatch[1], curIndex, index);
- continue
- }
-
- // Start tag:
- var startTagMatch = parseStartTag();
- if (startTagMatch) {
- handleStartTag(startTagMatch);
- continue
- }
- }
-
- var text = void 0;
- if (textEnd >= 0) {
- text = html.substring(0, textEnd);
- advance(textEnd);
- } else {
- text = html;
- html = '';
- }
-
- if (options.chars) {
- options.chars(text);
- }
- } else {
- var stackedTag = lastTag.toLowerCase();
- var reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)(</' + stackedTag + '[^>]*>)', 'i'));
- var endTagLength = 0;
- var rest = html.replace(reStackedTag, function (all, text, endTag) {
- endTagLength = endTag.length;
- if (stackedTag !== 'script' && stackedTag !== 'style' && stackedTag !== 'noscript') {
- text = text
- .replace(/<!--([\s\S]*?)-->/g, '$1')
- .replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, '$1');
- }
- if (options.chars) {
- options.chars(text);
- }
- return ''
- });
- index += html.length - rest.length;
- html = rest;
- parseEndTag('</' + stackedTag + '>', stackedTag, index - endTagLength, index);
- }
-
- if (html === last) {
- throw new Error('Error parsing template:\n\n' + html)
- }
- }
-
- // Clean up any remaining tags
- parseEndTag();
-
- function advance (n) {
- index += n;
- html = html.substring(n);
- }
-
- function parseStartTag () {
- var start = html.match(startTagOpen);
- if (start) {
- var match = {
- tagName: start[1],
- attrs: [],
- start: index
- };
- advance(start[0].length);
- var end, attr;
- while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
- advance(attr[0].length);
- match.attrs.push(attr);
- }
- if (end) {
- match.unarySlash = end[1];
- advance(end[0].length);
- match.end = index;
- return match
- }
- }
- }
-
- function handleStartTag (match) {
- var tagName = match.tagName;
- var unarySlash = match.unarySlash;
-
- if (expectHTML) {
- if (lastTag === 'p' && isNonPhrasingTag(tagName)) {
- parseEndTag('', lastTag);
- }
- if (canBeLeftOpenTag(tagName) && lastTag === tagName) {
- parseEndTag('', tagName);
- }
- }
-
- var unary = isUnaryTag$$1(tagName) || tagName === 'html' && lastTag === 'head' || !!unarySlash;
-
- var l = match.attrs.length;
- var attrs = new Array(l);
- for (var i = 0; i < l; i++) {
- var args = match.attrs[i];
- // hackish work around FF bug https://bugzilla.mozilla.org/show_bug.cgi?id=369778
- if (IS_REGEX_CAPTURING_BROKEN && args[0].indexOf('""') === -1) {
- if (args[3] === '') { delete args[3]; }
- if (args[4] === '') { delete args[4]; }
- if (args[5] === '') { delete args[5]; }
- }
- var value = args[3] || args[4] || args[5] || '';
- attrs[i] = {
- name: args[1],
- value: decodeAttr(
- value,
- options.shouldDecodeNewlines
- )
- };
- }
-
- if (!unary) {
- stack.push({ tag: tagName, attrs: attrs });
- lastTag = tagName;
- unarySlash = '';
- }
-
- if (options.start) {
- options.start(tagName, attrs, unary, match.start, match.end);
- }
- }
-
- function parseEndTag (tag, tagName, start, end) {
- var pos;
- if (start == null) { start = index; }
- if (end == null) { end = index; }
-
- // Find the closest opened tag of the same type
- if (tagName) {
- var needle = tagName.toLowerCase();
- for (pos = stack.length - 1; pos >= 0; pos--) {
- if (stack[pos].tag.toLowerCase() === needle) {
- break
- }
- }
- } else {
- // If no tag name is provided, clean shop
- pos = 0;
- }
-
- if (pos >= 0) {
- // Close all the open elements, up the stack
- for (var i = stack.length - 1; i >= pos; i--) {
- if (options.end) {
- options.end(stack[i].tag, start, end);
- }
- }
-
- // Remove the open elements from the stack
- stack.length = pos;
- lastTag = pos && stack[pos - 1].tag;
- } else if (tagName.toLowerCase() === 'br') {
- if (options.start) {
- options.start(tagName, [], true, start, end);
- }
- } else if (tagName.toLowerCase() === 'p') {
- if (options.start) {
- options.start(tagName, [], false, start, end);
- }
- if (options.end) {
- options.end(tagName, start, end);
- }
- }
- }
-}
-
-/* */
-
-function parseFilters (exp) {
- var inSingle = false;
- var inDouble = false;
- var curly = 0;
- var square = 0;
- var paren = 0;
- var lastFilterIndex = 0;
- var c, prev, i, expression, filters;
-
- for (i = 0; i < exp.length; i++) {
- prev = c;
- c = exp.charCodeAt(i);
- if (inSingle) {
- // check single quote
- if (c === 0x27 && prev !== 0x5C) { inSingle = !inSingle; }
- } else if (inDouble) {
- // check double quote
- if (c === 0x22 && prev !== 0x5C) { inDouble = !inDouble; }
- } else if (
- c === 0x7C && // pipe
- exp.charCodeAt(i + 1) !== 0x7C &&
- exp.charCodeAt(i - 1) !== 0x7C &&
- !curly && !square && !paren
- ) {
- if (expression === undefined) {
- // first filter, end of expression
- lastFilterIndex = i + 1;
- expression = exp.slice(0, i).trim();
- } else {
- pushFilter();
- }
- } else {
- switch (c) {
- case 0x22: inDouble = true; break // "
- case 0x27: inSingle = true; break // '
- case 0x28: paren++; break // (
- case 0x29: paren--; break // )
- case 0x5B: square++; break // [
- case 0x5D: square--; break // ]
- case 0x7B: curly++; break // {
- case 0x7D: curly--; break // }
- }
- }
- }
-
- if (expression === undefined) {
- expression = exp.slice(0, i).trim();
- } else if (lastFilterIndex !== 0) {
- pushFilter();
- }
-
- function pushFilter () {
- (filters || (filters = [])).push(exp.slice(lastFilterIndex, i).trim());
- lastFilterIndex = i + 1;
- }
-
- if (filters) {
- for (i = 0; i < filters.length; i++) {
- expression = wrapFilter(expression, filters[i]);
- }
- }
-
- return expression
-}
-
-function wrapFilter (exp, filter) {
- var i = filter.indexOf('(');
- if (i < 0) {
- // _f: resolveFilter
- return ("_f(\"" + filter + "\")(" + exp + ")")
- } else {
- var name = filter.slice(0, i);
- var args = filter.slice(i + 1);
- return ("_f(\"" + name + "\")(" + exp + "," + args)
- }
-}
-
-/* */
-
-var defaultTagRE = /\{\{((?:.|\n)+?)\}\}/g;
-var regexEscapeRE = /[-.*+?^${}()|[\]\/\\]/g;
-
-var buildRegex = cached(function (delimiters) {
- var open = delimiters[0].replace(regexEscapeRE, '\\$&');
- var close = delimiters[1].replace(regexEscapeRE, '\\$&');
- return new RegExp(open + '((?:.|\\n)+?)' + close, 'g')
-});
-
-function parseText (
- text,
- delimiters
-) {
- var tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE;
- if (!tagRE.test(text)) {
- return
- }
- var tokens = [];
- var lastIndex = tagRE.lastIndex = 0;
- var match, index;
- while ((match = tagRE.exec(text))) {
- index = match.index;
- // push text token
- if (index > lastIndex) {
- tokens.push(JSON.stringify(text.slice(lastIndex, index)));
- }
- // tag token
- var exp = parseFilters(match[1].trim());
- tokens.push(("_s(" + exp + ")"));
- lastIndex = index + match[0].length;
- }
- if (lastIndex < text.length) {
- tokens.push(JSON.stringify(text.slice(lastIndex)));
- }
- return tokens.join('+')
-}
-
-/* */
-
-function baseWarn (msg) {
- console.error(("[Vue parser]: " + msg));
-}
-
-function pluckModuleFunction (
- modules,
- key
-) {
- return modules
- ? modules.map(function (m) { return m[key]; }).filter(function (_) { return _; })
- : []
-}
-
-function addProp (el, name, value) {
- (el.props || (el.props = [])).push({ name: name, value: value });
-}
-
-function addAttr (el, name, value) {
- (el.attrs || (el.attrs = [])).push({ name: name, value: value });
-}
-
-function addDirective (
- el,
- name,
- rawName,
- value,
- arg,
- modifiers
-) {
- (el.directives || (el.directives = [])).push({ name: name, rawName: rawName, value: value, arg: arg, modifiers: modifiers });
-}
-
-function addHandler (
- el,
- name,
- value,
- modifiers,
- important
-) {
- // check capture modifier
- if (modifiers && modifiers.capture) {
- delete modifiers.capture;
- name = '!' + name; // mark the event as captured
- }
- var events;
- if (modifiers && modifiers.native) {
- delete modifiers.native;
- events = el.nativeEvents || (el.nativeEvents = {});
- } else {
- events = el.events || (el.events = {});
- }
- var newHandler = { value: value, modifiers: modifiers };
- var handlers = events[name];
- /* istanbul ignore if */
- if (Array.isArray(handlers)) {
- important ? handlers.unshift(newHandler) : handlers.push(newHandler);
- } else if (handlers) {
- events[name] = important ? [newHandler, handlers] : [handlers, newHandler];
- } else {
- events[name] = newHandler;
- }
-}
-
-function getBindingAttr (
- el,
- name,
- getStatic
-) {
- var dynamicValue =
- getAndRemoveAttr(el, ':' + name) ||
- getAndRemoveAttr(el, 'v-bind:' + name);
- if (dynamicValue != null) {
- return dynamicValue
- } else if (getStatic !== false) {
- var staticValue = getAndRemoveAttr(el, name);
- if (staticValue != null) {
- return JSON.stringify(staticValue)
- }
- }
-}
-
-function getAndRemoveAttr (el, name) {
- var val;
- if ((val = el.attrsMap[name]) != null) {
- var list = el.attrsList;
- for (var i = 0, l = list.length; i < l; i++) {
- if (list[i].name === name) {
- list.splice(i, 1);
- break
- }
- }
- }
- return val
-}
-
-/* */
-
-var dirRE = /^v-|^@|^:/;
-var forAliasRE = /(.*?)\s+(?:in|of)\s+(.*)/;
-var forIteratorRE = /\(([^,]*),([^,]*)(?:,([^,]*))?\)/;
-var bindRE = /^:|^v-bind:/;
-var onRE = /^@|^v-on:/;
-var argRE = /:(.*)$/;
-var modifierRE = /\.[^\.]+/g;
-var specialNewlineRE = /\u2028|\u2029/g;
-
-var decodeHTMLCached = cached(decode);
-
-// configurable state
-var warn$1;
-var platformGetTagNamespace;
-var platformMustUseProp;
-var platformIsPreTag;
-var preTransforms;
-var transforms;
-var postTransforms;
-var delimiters;
-
-/**
- * Convert HTML string to AST.
- */
-function parse (
- template,
- options
-) {
- warn$1 = options.warn || baseWarn;
- platformGetTagNamespace = options.getTagNamespace || no;
- platformMustUseProp = options.mustUseProp || no;
- platformIsPreTag = options.isPreTag || no;
- preTransforms = pluckModuleFunction(options.modules, 'preTransformNode');
- transforms = pluckModuleFunction(options.modules, 'transformNode');
- postTransforms = pluckModuleFunction(options.modules, 'postTransformNode');
- delimiters = options.delimiters;
- var stack = [];
- var preserveWhitespace = options.preserveWhitespace !== false;
- var root;
- var currentParent;
- var inVPre = false;
- var inPre = false;
- var warned = false;
- parseHTML(template, {
- expectHTML: options.expectHTML,
- isUnaryTag: options.isUnaryTag,
- shouldDecodeNewlines: options.shouldDecodeNewlines,
- start: function start (tag, attrs, unary) {
- // check namespace.
- // inherit parent ns if there is one
- var ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag);
-
- // handle IE svg bug
- /* istanbul ignore if */
- if (options.isIE && ns === 'svg') {
- attrs = guardIESVGBug(attrs);
- }
-
- var element = {
- type: 1,
- tag: tag,
- attrsList: attrs,
- attrsMap: makeAttrsMap(attrs, options.isIE),
- parent: currentParent,
- children: []
- };
- if (ns) {
- element.ns = ns;
- }
-
- if ("client" !== 'server' && isForbiddenTag(element)) {
- element.forbidden = true;
- "development" !== 'production' && warn$1(
- 'Templates should only be responsible for mapping the state to the ' +
- 'UI. Avoid placing tags with side-effects in your templates, such as ' +
- "<" + tag + ">."
- );
- }
-
- // apply pre-transforms
- for (var i = 0; i < preTransforms.length; i++) {
- preTransforms[i](element, options);
- }
-
- if (!inVPre) {
- processPre(element);
- if (element.pre) {
- inVPre = true;
- }
- }
- if (platformIsPreTag(element.tag)) {
- inPre = true;
- }
- if (inVPre) {
- processRawAttrs(element);
- } else {
- processFor(element);
- processIf(element);
- processOnce(element);
- processKey(element);
-
- // determine whether this is a plain element after
- // removing structural attributes
- element.plain = !element.key && !attrs.length;
-
- processRef(element);
- processSlot(element);
- processComponent(element);
- for (var i$1 = 0; i$1 < transforms.length; i$1++) {
- transforms[i$1](element, options);
- }
- processAttrs(element);
- }
-
- function checkRootConstraints (el) {
- {
- if (el.tag === 'slot' || el.tag === 'template') {
- warn$1(
- "Cannot use <" + (el.tag) + "> as component root element because it may " +
- 'contain multiple nodes:\n' + template
- );
- }
- if (el.attrsMap.hasOwnProperty('v-for')) {
- warn$1(
- 'Cannot use v-for on stateful component root element because ' +
- 'it renders multiple elements:\n' + template
- );
- }
- }
- }
-
- // tree management
- if (!root) {
- root = element;
- checkRootConstraints(root);
- } else if ("development" !== 'production' && !stack.length && !warned) {
- // allow 2 root elements with v-if and v-else
- if (root.if && element.else) {
- checkRootConstraints(element);
- root.elseBlock = element;
- } else {
- warned = true;
- warn$1(
- ("Component template should contain exactly one root element:\n\n" + template)
- );
- }
- }
- if (currentParent && !element.forbidden) {
- if (element.else) {
- processElse(element, currentParent);
- } else {
- currentParent.children.push(element);
- element.parent = currentParent;
- }
- }
- if (!unary) {
- currentParent = element;
- stack.push(element);
- }
- // apply post-transforms
- for (var i$2 = 0; i$2 < postTransforms.length; i$2++) {
- postTransforms[i$2](element, options);
- }
- },
-
- end: function end () {
- // remove trailing whitespace
- var element = stack[stack.length - 1];
- var lastNode = element.children[element.children.length - 1];
- if (lastNode && lastNode.type === 3 && lastNode.text === ' ') {
- element.children.pop();
- }
- // pop stack
- stack.length -= 1;
- currentParent = stack[stack.length - 1];
- // check pre state
- if (element.pre) {
- inVPre = false;
- }
- if (platformIsPreTag(element.tag)) {
- inPre = false;
- }
- },
-
- chars: function chars (text) {
- if (!currentParent) {
- if ("development" !== 'production' && !warned && text === template) {
- warned = true;
- warn$1(
- 'Component template requires a root element, rather than just text:\n\n' + template
- );
- }
- return
- }
- text = inPre || text.trim()
- ? decodeHTMLCached(text)
- // only preserve whitespace if its not right after a starting tag
- : preserveWhitespace && currentParent.children.length ? ' ' : '';
- if (text) {
- var expression;
- if (!inVPre && text !== ' ' && (expression = parseText(text, delimiters))) {
- currentParent.children.push({
- type: 2,
- expression: expression,
- text: text
- });
- } else {
- // #3895 special character
- text = text.replace(specialNewlineRE, '');
- currentParent.children.push({
- type: 3,
- text: text
- });
- }
- }
- }
- });
- return root
-}
-
-function processPre (el) {
- if (getAndRemoveAttr(el, 'v-pre') != null) {
- el.pre = true;
- }
-}
-
-function processRawAttrs (el) {
- var l = el.attrsList.length;
- if (l) {
- var attrs = el.attrs = new Array(l);
- for (var i = 0; i < l; i++) {
- attrs[i] = {
- name: el.attrsList[i].name,
- value: JSON.stringify(el.attrsList[i].value)
- };
- }
- } else if (!el.pre) {
- // non root node in pre blocks with no attributes
- el.plain = true;
- }
-}
-
-function processKey (el) {
- var exp = getBindingAttr(el, 'key');
- if (exp) {
- if ("development" !== 'production' && el.tag === 'template') {
- warn$1("<template> cannot be keyed. Place the key on real elements instead.");
- }
- el.key = exp;
- }
-}
-
-function processRef (el) {
- var ref = getBindingAttr(el, 'ref');
- if (ref) {
- el.ref = ref;
- el.refInFor = checkInFor(el);
- }
-}
-
-function processFor (el) {
- var exp;
- if ((exp = getAndRemoveAttr(el, 'v-for'))) {
- var inMatch = exp.match(forAliasRE);
- if (!inMatch) {
- "development" !== 'production' && warn$1(
- ("Invalid v-for expression: " + exp)
- );
- return
- }
- el.for = inMatch[2].trim();
- var alias = inMatch[1].trim();
- var iteratorMatch = alias.match(forIteratorRE);
- if (iteratorMatch) {
- el.alias = iteratorMatch[1].trim();
- el.iterator1 = iteratorMatch[2].trim();
- if (iteratorMatch[3]) {
- el.iterator2 = iteratorMatch[3].trim();
- }
- } else {
- el.alias = alias;
- }
- }
-}
-
-function processIf (el) {
- var exp = getAndRemoveAttr(el, 'v-if');
- if (exp) {
- el.if = exp;
- }
- if (getAndRemoveAttr(el, 'v-else') != null) {
- el.else = true;
- }
-}
-
-function processElse (el, parent) {
- var prev = findPrevElement(parent.children);
- if (prev && prev.if) {
- prev.elseBlock = el;
- } else {
- warn$1(
- ("v-else used on element <" + (el.tag) + "> without corresponding v-if.")
- );
- }
-}
-
-function processOnce (el) {
- var once = getAndRemoveAttr(el, 'v-once');
- if (once != null) {
- el.once = true;
- }
-}
-
-function processSlot (el) {
- if (el.tag === 'slot') {
- el.slotName = getBindingAttr(el, 'name');
- } else {
- var slotTarget = getBindingAttr(el, 'slot');
- if (slotTarget) {
- el.slotTarget = slotTarget;
- }
- }
-}
-
-function processComponent (el) {
- var binding;
- if ((binding = getBindingAttr(el, 'is'))) {
- el.component = binding;
- }
- if (getAndRemoveAttr(el, 'inline-template') != null) {
- el.inlineTemplate = true;
- }
-}
-
-function processAttrs (el) {
- var list = el.attrsList;
- var i, l, name, rawName, value, arg, modifiers, isProp;
- for (i = 0, l = list.length; i < l; i++) {
- name = rawName = list[i].name;
- value = list[i].value;
- if (dirRE.test(name)) {
- // mark element as dynamic
- el.hasBindings = true;
- // modifiers
- modifiers = parseModifiers(name);
- if (modifiers) {
- name = name.replace(modifierRE, '');
- }
- if (bindRE.test(name)) { // v-bind
- name = name.replace(bindRE, '');
- if (modifiers && modifiers.prop) {
- isProp = true;
- name = camelize(name);
- if (name === 'innerHtml') { name = 'innerHTML'; }
- }
- if (isProp || platformMustUseProp(name)) {
- addProp(el, name, value);
- } else {
- addAttr(el, name, value);
- }
- } else if (onRE.test(name)) { // v-on
- name = name.replace(onRE, '');
- addHandler(el, name, value, modifiers);
- } else { // normal directives
- name = name.replace(dirRE, '');
- // parse arg
- var argMatch = name.match(argRE);
- if (argMatch && (arg = argMatch[1])) {
- name = name.slice(0, -(arg.length + 1));
- }
- addDirective(el, name, rawName, value, arg, modifiers);
- if ("development" !== 'production' && name === 'model') {
- checkForAliasModel(el, value);
- }
- }
- } else {
- // literal attribute
- {
- var expression = parseText(value, delimiters);
- if (expression) {
- warn$1(
- name + "=\"" + value + "\": " +
- 'Interpolation inside attributes has been deprecated. ' +
- 'Use v-bind or the colon shorthand instead.'
- );
- }
- }
- addAttr(el, name, JSON.stringify(value));
- }
- }
-}
-
-function checkInFor (el) {
- var parent = el;
- while (parent) {
- if (parent.for !== undefined) {
- return true
- }
- parent = parent.parent;
- }
- return false
-}
-
-function parseModifiers (name) {
- var match = name.match(modifierRE);
- if (match) {
- var ret = {};
- match.forEach(function (m) { ret[m.slice(1)] = true; });
- return ret
- }
-}
-
-function makeAttrsMap (attrs, isIE) {
- var map = {};
- for (var i = 0, l = attrs.length; i < l; i++) {
- if ("development" !== 'production' && map[attrs[i].name] && !isIE) {
- warn$1('duplicate attribute: ' + attrs[i].name);
- }
- map[attrs[i].name] = attrs[i].value;
- }
- return map
-}
-
-function findPrevElement (children) {
- var i = children.length;
- while (i--) {
- if (children[i].tag) { return children[i] }
- }
-}
-
-function isForbiddenTag (el) {
- return (
- el.tag === 'style' ||
- (el.tag === 'script' && (
- !el.attrsMap.type ||
- el.attrsMap.type === 'text/javascript'
- ))
- )
-}
-
-var ieNSBug = /^xmlns:NS\d+/;
-var ieNSPrefix = /^NS\d+:/;
-
-/* istanbul ignore next */
-function guardIESVGBug (attrs) {
- var res = [];
- for (var i = 0; i < attrs.length; i++) {
- var attr = attrs[i];
- if (!ieNSBug.test(attr.name)) {
- attr.name = attr.name.replace(ieNSPrefix, '');
- res.push(attr);
- }
- }
- return res
-}
-
-function checkForAliasModel (el, value) {
- var _el = el;
- while (_el) {
- if (_el.for && _el.alias === value) {
- warn$1(
- "<" + (el.tag) + " v-model=\"" + value + "\">: " +
- "You are binding v-model directly to a v-for iteration alias. " +
- "This will not be able to modify the v-for source array because " +
- "writing to the alias is like modifying a function local variable. " +
- "Consider using an array of objects and use v-model on an object property instead."
- );
- }
- _el = _el.parent;
- }
-}
-
-/* */
-
-var isStaticKey;
-var isPlatformReservedTag;
-
-var genStaticKeysCached = cached(genStaticKeys$1);
-
-/**
- * Goal of the optimizier: walk the generated template AST tree
- * and detect sub-trees that are purely static, i.e. parts of
- * the DOM that never needs to change.
- *
- * Once we detect these sub-trees, we can:
- *
- * 1. Hoist them into constants, so that we no longer need to
- * create fresh nodes for them on each re-render;
- * 2. Completely skip them in the patching process.
- */
-function optimize (root, options) {
- if (!root) { return }
- isStaticKey = genStaticKeysCached(options.staticKeys || '');
- isPlatformReservedTag = options.isReservedTag || (function () { return false; });
- // first pass: mark all non-static nodes.
- markStatic(root);
- // second pass: mark static roots.
- markStaticRoots(root, false);
-}
-
-function genStaticKeys$1 (keys) {
- return makeMap(
- 'type,tag,attrsList,attrsMap,plain,parent,children,attrs' +
- (keys ? ',' + keys : '')
- )
-}
-
-function markStatic (node) {
- node.static = isStatic(node);
- if (node.type === 1) {
- for (var i = 0, l = node.children.length; i < l; i++) {
- var child = node.children[i];
- markStatic(child);
- if (!child.static) {
- node.static = false;
- }
- }
- }
-}
-
-function markStaticRoots (node, isInFor) {
- if (node.type === 1) {
- if (node.once || node.static) {
- node.staticRoot = true;
- node.staticInFor = isInFor;
- return
- }
- if (node.children) {
- for (var i = 0, l = node.children.length; i < l; i++) {
- markStaticRoots(node.children[i], isInFor || !!node.for);
- }
- }
- }
-}
-
-function isStatic (node) {
- if (node.type === 2) { // expression
- return false
- }
- if (node.type === 3) { // text
- return true
- }
- return !!(node.pre || (
- !node.hasBindings && // no dynamic bindings
- !node.if && !node.for && // not v-if or v-for or v-else
- !isBuiltInTag(node.tag) && // not a built-in
- isPlatformReservedTag(node.tag) && // not a component
- !isDirectChildOfTemplateFor(node) &&
- Object.keys(node).every(isStaticKey)
- ))
-}
-
-function isDirectChildOfTemplateFor (node) {
- while (node.parent) {
- node = node.parent;
- if (node.tag !== 'template') {
- return false
- }
- if (node.for) {
- return true
- }
- }
- return false
-}
-
-/* */
-
-var simplePathRE = /^\s*[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['.*?'\]|\[".*?"\]|\[\d+\]|\[[A-Za-z_$][\w$]*\])*\s*$/;
-
-// keyCode aliases
-var keyCodes = {
- esc: 27,
- tab: 9,
- enter: 13,
- space: 32,
- up: 38,
- left: 37,
- right: 39,
- down: 40,
- 'delete': [8, 46]
-};
-
-var modifierCode = {
- stop: '$event.stopPropagation();',
- prevent: '$event.preventDefault();',
- self: 'if($event.target !== $event.currentTarget)return;'
-};
-
-function genHandlers (events, native) {
- var res = native ? 'nativeOn:{' : 'on:{';
- for (var name in events) {
- res += "\"" + name + "\":" + (genHandler(events[name])) + ",";
- }
- return res.slice(0, -1) + '}'
-}
-
-function genHandler (
- handler
-) {
- if (!handler) {
- return 'function(){}'
- } else if (Array.isArray(handler)) {
- return ("[" + (handler.map(genHandler).join(',')) + "]")
- } else if (!handler.modifiers) {
- return simplePathRE.test(handler.value)
- ? handler.value
- : ("function($event){" + (handler.value) + "}")
- } else {
- var code = '';
- var keys = [];
- for (var key in handler.modifiers) {
- if (modifierCode[key]) {
- code += modifierCode[key];
- } else {
- keys.push(key);
- }
- }
- if (keys.length) {
- code = genKeyFilter(keys) + code;
- }
- var handlerCode = simplePathRE.test(handler.value)
- ? handler.value + '($event)'
- : handler.value;
- return 'function($event){' + code + handlerCode + '}'
- }
-}
-
-function genKeyFilter (keys) {
- var code = keys.length === 1
- ? normalizeKeyCode(keys[0])
- : Array.prototype.concat.apply([], keys.map(normalizeKeyCode));
- if (Array.isArray(code)) {
- return ("if(" + (code.map(function (c) { return ("$event.keyCode!==" + c); }).join('&&')) + ")return;")
- } else {
- return ("if($event.keyCode!==" + code + ")return;")
- }
-}
-
-function normalizeKeyCode (key) {
- return (
- parseInt(key, 10) || // number keyCode
- keyCodes[key] || // built-in alias
- ("_k(" + (JSON.stringify(key)) + ")") // custom alias
- )
-}
-
-/* */
-
-function bind$2 (el, dir) {
- el.wrapData = function (code) {
- return ("_b(" + code + "," + (dir.value) + (dir.modifiers && dir.modifiers.prop ? ',true' : '') + ")")
- };
-}
-
-var baseDirectives = {
- bind: bind$2,
- cloak: noop
-};
-
-/* */
-
-// configurable state
-var warn$2;
-var transforms$1;
-var dataGenFns;
-var platformDirectives$1;
-var staticRenderFns;
-var currentOptions;
-
-function generate (
- ast,
- options
-) {
- // save previous staticRenderFns so generate calls can be nested
- var prevStaticRenderFns = staticRenderFns;
- var currentStaticRenderFns = staticRenderFns = [];
- currentOptions = options;
- warn$2 = options.warn || baseWarn;
- transforms$1 = pluckModuleFunction(options.modules, 'transformCode');
- dataGenFns = pluckModuleFunction(options.modules, 'genData');
- platformDirectives$1 = options.directives || {};
- var code = ast ? genElement(ast) : '_h("div")';
- staticRenderFns = prevStaticRenderFns;
- return {
- render: ("with(this){return " + code + "}"),
- staticRenderFns: currentStaticRenderFns
- }
-}
-
-function genElement (el) {
- if (el.staticRoot && !el.staticProcessed) {
- // hoist static sub-trees out
- el.staticProcessed = true;
- staticRenderFns.push(("with(this){return " + (genElement(el)) + "}"));
- return ("_m(" + (staticRenderFns.length - 1) + (el.staticInFor ? ',true' : '') + ")")
- } else if (el.for && !el.forProcessed) {
- return genFor(el)
- } else if (el.if && !el.ifProcessed) {
- return genIf(el)
- } else if (el.tag === 'template' && !el.slotTarget) {
- return genChildren(el) || 'void 0'
- } else if (el.tag === 'slot') {
- return genSlot(el)
- } else {
- // component or element
- var code;
- if (el.component) {
- code = genComponent(el);
- } else {
- var data = genData(el);
- var children = el.inlineTemplate ? null : genChildren(el);
- code = "_h('" + (el.tag) + "'" + (data ? ("," + data) : '') + (children ? ("," + children) : '') + ")";
- }
- // module transforms
- for (var i = 0; i < transforms$1.length; i++) {
- code = transforms$1[i](el, code);
- }
- return code
- }
-}
-
-function genIf (el) {
- var exp = el.if;
- el.ifProcessed = true; // avoid recursion
- return ("(" + exp + ")?" + (genElement(el)) + ":" + (genElse(el)))
-}
-
-function genElse (el) {
- return el.elseBlock
- ? genElement(el.elseBlock)
- : '_e()'
-}
-
-function genFor (el) {
- var exp = el.for;
- var alias = el.alias;
- var iterator1 = el.iterator1 ? ("," + (el.iterator1)) : '';
- var iterator2 = el.iterator2 ? ("," + (el.iterator2)) : '';
- el.forProcessed = true; // avoid recursion
- return "_l((" + exp + ")," +
- "function(" + alias + iterator1 + iterator2 + "){" +
- "return " + (genElement(el)) +
- '})'
-}
-
-function genData (el) {
- if (el.plain) {
- return
- }
-
- var data = '{';
-
- // directives first.
- // directives may mutate the el's other properties before they are generated.
- var dirs = genDirectives(el);
- if (dirs) { data += dirs + ','; }
-
- // key
- if (el.key) {
- data += "key:" + (el.key) + ",";
- }
- // ref
- if (el.ref) {
- data += "ref:" + (el.ref) + ",";
- }
- if (el.refInFor) {
- data += "refInFor:true,";
- }
- // record original tag name for components using "is" attribute
- if (el.component) {
- data += "tag:\"" + (el.tag) + "\",";
- }
- // slot target
- if (el.slotTarget) {
- data += "slot:" + (el.slotTarget) + ",";
- }
- // module data generation functions
- for (var i = 0; i < dataGenFns.length; i++) {
- data += dataGenFns[i](el);
- }
- // attributes
- if (el.attrs) {
- data += "attrs:{" + (genProps(el.attrs)) + "},";
- }
- // DOM props
- if (el.props) {
- data += "domProps:{" + (genProps(el.props)) + "},";
- }
- // event handlers
- if (el.events) {
- data += (genHandlers(el.events)) + ",";
- }
- if (el.nativeEvents) {
- data += (genHandlers(el.nativeEvents, true)) + ",";
- }
- // inline-template
- if (el.inlineTemplate) {
- var ast = el.children[0];
- if ("development" !== 'production' && (
- el.children.length > 1 || ast.type !== 1
- )) {
- warn$2('Inline-template components must have exactly one child element.');
- }
- if (ast.type === 1) {
- var inlineRenderFns = generate(ast, currentOptions);
- data += "inlineTemplate:{render:function(){" + (inlineRenderFns.render) + "},staticRenderFns:[" + (inlineRenderFns.staticRenderFns.map(function (code) { return ("function(){" + code + "}"); }).join(',')) + "]}";
- }
- }
- data = data.replace(/,$/, '') + '}';
- // v-bind data wrap
- if (el.wrapData) {
- data = el.wrapData(data);
- }
- return data
-}
-
-function genDirectives (el) {
- var dirs = el.directives;
- if (!dirs) { return }
- var res = 'directives:[';
- var hasRuntime = false;
- var i, l, dir, needRuntime;
- for (i = 0, l = dirs.length; i < l; i++) {
- dir = dirs[i];
- needRuntime = true;
- var gen = platformDirectives$1[dir.name] || baseDirectives[dir.name];
- if (gen) {
- // compile-time directive that manipulates AST.
- // returns true if it also needs a runtime counterpart.
- needRuntime = !!gen(el, dir, warn$2);
- }
- if (needRuntime) {
- hasRuntime = true;
- res += "{name:\"" + (dir.name) + "\",rawName:\"" + (dir.rawName) + "\"" + (dir.value ? (",value:(" + (dir.value) + "),expression:" + (JSON.stringify(dir.value))) : '') + (dir.arg ? (",arg:\"" + (dir.arg) + "\"") : '') + (dir.modifiers ? (",modifiers:" + (JSON.stringify(dir.modifiers))) : '') + "},";
- }
- }
- if (hasRuntime) {
- return res.slice(0, -1) + ']'
- }
-}
-
-function genChildren (el) {
- if (el.children.length) {
- return '[' + el.children.map(genNode).join(',') + ']'
- }
-}
-
-function genNode (node) {
- if (node.type === 1) {
- return genElement(node)
- } else {
- return genText(node)
- }
-}
-
-function genText (text) {
- return text.type === 2
- ? text.expression // no need for () because already wrapped in _s()
- : JSON.stringify(text.text)
-}
-
-function genSlot (el) {
- var slotName = el.slotName || '"default"';
- var children = genChildren(el);
- return children
- ? ("_t(" + slotName + "," + children + ")")
- : ("_t(" + slotName + ")")
-}
-
-function genComponent (el) {
- var children = el.inlineTemplate ? null : genChildren(el);
- return ("_h(" + (el.component) + "," + (genData(el)) + (children ? ("," + children) : '') + ")")
-}
-
-function genProps (props) {
- var res = '';
- for (var i = 0; i < props.length; i++) {
- var prop = props[i];
- res += "\"" + (prop.name) + "\":" + (prop.value) + ",";
- }
- return res.slice(0, -1)
-}
-
-/* */
-
-/**
- * Compile a template.
- */
-function compile$1 (
- template,
- options
-) {
- var ast = parse(template.trim(), options);
- optimize(ast, options);
- var code = generate(ast, options);
- return {
- ast: ast,
- render: code.render,
- staticRenderFns: code.staticRenderFns
- }
-}
-
-/* */
-
-// operators like typeof, instanceof and in are allowed
-var prohibitedKeywordRE = new RegExp('\\b' + (
- 'do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,' +
- 'super,throw,while,yield,delete,export,import,return,switch,default,' +
- 'extends,finally,continue,debugger,function,arguments'
-).split(',').join('\\b|\\b') + '\\b');
-// check valid identifier for v-for
-var identRE = /[A-Za-z_$][\w$]*/;
-// strip strings in expressions
-var stripStringRE = /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*\$\{|\}(?:[^`\\]|\\.)*`|`(?:[^`\\]|\\.)*`/g;
-
-// detect problematic expressions in a template
-function detectErrors (ast) {
- var errors = [];
- if (ast) {
- checkNode(ast, errors);
- }
- return errors
-}
-
-function checkNode (node, errors) {
- if (node.type === 1) {
- for (var name in node.attrsMap) {
- if (dirRE.test(name)) {
- var value = node.attrsMap[name];
- if (value) {
- if (name === 'v-for') {
- checkFor(node, ("v-for=\"" + value + "\""), errors);
- } else {
- checkExpression(value, (name + "=\"" + value + "\""), errors);
- }
- }
- }
- }
- if (node.children) {
- for (var i = 0; i < node.children.length; i++) {
- checkNode(node.children[i], errors);
- }
- }
- } else if (node.type === 2) {
- checkExpression(node.expression, node.text, errors);
- }
-}
-
-function checkFor (node, text, errors) {
- checkExpression(node.for || '', text, errors);
- checkIdentifier(node.alias, 'v-for alias', text, errors);
- checkIdentifier(node.iterator1, 'v-for iterator', text, errors);
- checkIdentifier(node.iterator2, 'v-for iterator', text, errors);
-}
-
-function checkIdentifier (ident, type, text, errors) {
- if (typeof ident === 'string' && !identRE.test(ident)) {
- errors.push(("- invalid " + type + " \"" + ident + "\" in expression: " + text));
- }
-}
-
-function checkExpression (exp, text, errors) {
- try {
- new Function(("return " + exp));
- } catch (e) {
- var keywordMatch = exp.replace(stripStringRE, '').match(prohibitedKeywordRE);
- if (keywordMatch) {
- errors.push(
- "- avoid using JavaScript keyword as property name: " +
- "\"" + (keywordMatch[0]) + "\" in expression " + text
- );
- } else {
- errors.push(("- invalid expression: " + text));
- }
- }
-}
-
-/* */
-
-function transformNode (el, options) {
- var warn = options.warn || baseWarn;
- var staticClass = getAndRemoveAttr(el, 'class');
- if ("development" !== 'production' && staticClass) {
- var expression = parseText(staticClass, options.delimiters);
- if (expression) {
- warn(
- "class=\"" + staticClass + "\": " +
- 'Interpolation inside attributes has been deprecated. ' +
- 'Use v-bind or the colon shorthand instead.'
- );
- }
- }
- if (staticClass) {
- el.staticClass = JSON.stringify(staticClass);
- }
- var classBinding = getBindingAttr(el, 'class', false /* getStatic */);
- if (classBinding) {
- el.classBinding = classBinding;
- }
-}
-
-function genData$1 (el) {
- var data = '';
- if (el.staticClass) {
- data += "staticClass:" + (el.staticClass) + ",";
- }
- if (el.classBinding) {
- data += "class:" + (el.classBinding) + ",";
- }
- return data
-}
-
-var klass$1 = {
- staticKeys: ['staticClass'],
- transformNode: transformNode,
- genData: genData$1
-};
-
-/* */
-
-function transformNode$1 (el) {
- var styleBinding = getBindingAttr(el, 'style', false /* getStatic */);
- if (styleBinding) {
- el.styleBinding = styleBinding;
- }
-}
-
-function genData$2 (el) {
- return el.styleBinding
- ? ("style:(" + (el.styleBinding) + "),")
- : ''
-}
-
-var style$1 = {
- transformNode: transformNode$1,
- genData: genData$2
-};
-
-var modules$1 = [
- klass$1,
- style$1
-];
-
-/* */
-
-var warn$3;
-
-function model$1 (
- el,
- dir,
- _warn
-) {
- warn$3 = _warn;
- var value = dir.value;
- var modifiers = dir.modifiers;
- var tag = el.tag;
- var type = el.attrsMap.type;
- {
- var dynamicType = el.attrsMap['v-bind:type'] || el.attrsMap[':type'];
- if (tag === 'input' && dynamicType) {
- warn$3(
- "<input :type=\"" + dynamicType + "\" v-model=\"" + value + "\">:\n" +
- "v-model does not support dynamic input types. Use v-if branches instead."
- );
- }
- }
- if (tag === 'select') {
- genSelect(el, value);
- } else if (tag === 'input' && type === 'checkbox') {
- genCheckboxModel(el, value);
- } else if (tag === 'input' && type === 'radio') {
- genRadioModel(el, value);
- } else {
- genDefaultModel(el, value, modifiers);
- }
- // ensure runtime directive metadata
- return true
-}
-
-function genCheckboxModel (el, value) {
- if ("development" !== 'production' &&
- el.attrsMap.checked != null) {
- warn$3(
- "<" + (el.tag) + " v-model=\"" + value + "\" checked>:\n" +
- "inline checked attributes will be ignored when using v-model. " +
- 'Declare initial values in the component\'s data option instead.'
- );
- }
- var valueBinding = getBindingAttr(el, 'value') || 'null';
- var trueValueBinding = getBindingAttr(el, 'true-value') || 'true';
- var falseValueBinding = getBindingAttr(el, 'false-value') || 'false';
- addProp(el, 'checked',
- "Array.isArray(" + value + ")" +
- "?_i(" + value + "," + valueBinding + ")>-1" +
- ":_q(" + value + "," + trueValueBinding + ")"
- );
- addHandler(el, 'change',
- "var $$a=" + value + "," +
- '$$el=$event.target,' +
- "$$c=$$el.checked?(" + trueValueBinding + "):(" + falseValueBinding + ");" +
- 'if(Array.isArray($$a)){' +
- "var $$v=" + valueBinding + "," +
- '$$i=_i($$a,$$v);' +
- "if($$c){$$i<0&&(" + value + "=$$a.concat($$v))}" +
- "else{$$i>-1&&(" + value + "=$$a.slice(0,$$i).concat($$a.slice($$i+1)))}" +
- "}else{" + value + "=$$c}",
- null, true
- );
-}
-
-function genRadioModel (el, value) {
- if ("development" !== 'production' &&
- el.attrsMap.checked != null) {
- warn$3(
- "<" + (el.tag) + " v-model=\"" + value + "\" checked>:\n" +
- "inline checked attributes will be ignored when using v-model. " +
- 'Declare initial values in the component\'s data option instead.'
- );
- }
- var valueBinding = getBindingAttr(el, 'value') || 'null';
- addProp(el, 'checked', ("_q(" + value + "," + valueBinding + ")"));
- addHandler(el, 'change', (value + "=" + valueBinding), null, true);
-}
-
-function genDefaultModel (
- el,
- value,
- modifiers
-) {
- {
- if (el.tag === 'input' && el.attrsMap.value) {
- warn$3(
- "<" + (el.tag) + " v-model=\"" + value + "\" value=\"" + (el.attrsMap.value) + "\">:\n" +
- 'inline value attributes will be ignored when using v-model. ' +
- 'Declare initial values in the component\'s data option instead.'
- );
- }
- if (el.tag === 'textarea' && el.children.length) {
- warn$3(
- "<textarea v-model=\"" + value + "\">:\n" +
- 'inline content inside <textarea> will be ignored when using v-model. ' +
- 'Declare initial values in the component\'s data option instead.'
- );
- }
- }
-
- var type = el.attrsMap.type;
- var ref = modifiers || {};
- var lazy = ref.lazy;
- var number = ref.number;
- var trim = ref.trim;
- var event = lazy || (isIE && type === 'range') ? 'change' : 'input';
- var needCompositionGuard = !lazy && type !== 'range';
- var isNative = el.tag === 'input' || el.tag === 'textarea';
-
- var valueExpression = isNative
- ? ("$event.target.value" + (trim ? '.trim()' : ''))
- : "$event";
- var code = number || type === 'number'
- ? (value + "=_n(" + valueExpression + ")")
- : (value + "=" + valueExpression);
- if (isNative && needCompositionGuard) {
- code = "if($event.target.composing)return;" + code;
- }
- // inputs with type="file" are read only and setting the input's
- // value will throw an error.
- if ("development" !== 'production' &&
- type === 'file') {
- warn$3(
- "<" + (el.tag) + " v-model=\"" + value + "\" type=\"file\">:\n" +
- "File inputs are read only. Use a v-on:change listener instead."
- );
- }
- addProp(el, 'value', isNative ? ("_s(" + value + ")") : ("(" + value + ")"));
- addHandler(el, event, code, null, true);
-}
-
-function genSelect (el, value) {
- {
- el.children.some(checkOptionWarning);
- }
- var code = value + "=Array.prototype.filter" +
- ".call($event.target.options,function(o){return o.selected})" +
- ".map(function(o){return \"_value\" in o ? o._value : o.value})" +
- (el.attrsMap.multiple == null ? '[0]' : '');
- addHandler(el, 'change', code, null, true);
-}
-
-function checkOptionWarning (option) {
- if (option.type === 1 &&
- option.tag === 'option' &&
- option.attrsMap.selected != null) {
- warn$3(
- "<select v-model=\"" + (option.parent.attrsMap['v-model']) + "\">:\n" +
- 'inline selected attributes on <option> will be ignored when using v-model. ' +
- 'Declare initial values in the component\'s data option instead.'
- );
- return true
- }
- return false
-}
-
-/* */
-
-function text (el, dir) {
- if (dir.value) {
- addProp(el, 'textContent', ("_s(" + (dir.value) + ")"));
- }
-}
-
-/* */
-
-function html (el, dir) {
- if (dir.value) {
- addProp(el, 'innerHTML', ("_s(" + (dir.value) + ")"));
- }
-}
-
-var directives$1 = {
- model: model$1,
- text: text,
- html: html
-};
-
-/* */
-
-var cache = Object.create(null);
-
-var baseOptions = {
- isIE: isIE,
- expectHTML: true,
- modules: modules$1,
- staticKeys: genStaticKeys(modules$1),
- directives: directives$1,
- isReservedTag: isReservedTag,
- isUnaryTag: isUnaryTag,
- mustUseProp: mustUseProp,
- getTagNamespace: getTagNamespace,
- isPreTag: isPreTag
-};
-
-function compile$$1 (
- template,
- options
-) {
- options = options
- ? extend(extend({}, baseOptions), options)
- : baseOptions;
- return compile$1(template, options)
-}
-
-function compileToFunctions (
- template,
- options,
- vm
-) {
- var _warn = (options && options.warn) || warn;
- // detect possible CSP restriction
- /* istanbul ignore if */
- {
- try {
- new Function('return 1');
- } catch (e) {
- if (e.toString().match(/unsafe-eval|CSP/)) {
- _warn(
- 'It seems you are using the standalone build of Vue.js in an ' +
- 'environment with Content Security Policy that prohibits unsafe-eval. ' +
- 'The template compiler cannot work in this environment. Consider ' +
- 'relaxing the policy to allow unsafe-eval or pre-compiling your ' +
- 'templates into render functions.'
- );
- }
- }
- }
- var key = options && options.delimiters
- ? String(options.delimiters) + template
- : template;
- if (cache[key]) {
- return cache[key]
- }
- var res = {};
- var compiled = compile$$1(template, options);
- res.render = makeFunction(compiled.render);
- var l = compiled.staticRenderFns.length;
- res.staticRenderFns = new Array(l);
- for (var i = 0; i < l; i++) {
- res.staticRenderFns[i] = makeFunction(compiled.staticRenderFns[i]);
- }
- {
- if (res.render === noop || res.staticRenderFns.some(function (fn) { return fn === noop; })) {
- _warn(
- "failed to compile template:\n\n" + template + "\n\n" +
- detectErrors(compiled.ast).join('\n') +
- '\n\n',
- vm
- );
- }
- }
- return (cache[key] = res)
-}
-
-function makeFunction (code) {
- try {
- return new Function(code)
- } catch (e) {
- return noop
- }
-}
-
-/* */
-
-var idToTemplate = cached(function (id) {
- var el = query(id);
- return el && el.innerHTML
-});
-
-var mount = Vue$3.prototype.$mount;
-Vue$3.prototype.$mount = function (
- el,
- hydrating
-) {
- el = el && query(el);
-
- /* istanbul ignore if */
- if (el === document.body || el === document.documentElement) {
- "development" !== 'production' && warn(
- "Do not mount Vue to <html> or <body> - mount to normal elements instead."
- );
- return this
- }
-
- var options = this.$options;
- // resolve template/el and convert to render function
- if (!options.render) {
- var template = options.template;
- if (template) {
- if (typeof template === 'string') {
- if (template.charAt(0) === '#') {
- template = idToTemplate(template);
- }
- } else if (template.nodeType) {
- template = template.innerHTML;
- } else {
- {
- warn('invalid template option:' + template, this);
- }
- return this
- }
- } else if (el) {
- template = getOuterHTML(el);
- }
- if (template) {
- var ref = compileToFunctions(template, {
- warn: warn,
- shouldDecodeNewlines: shouldDecodeNewlines,
- delimiters: options.delimiters
- }, this);
- var render = ref.render;
- var staticRenderFns = ref.staticRenderFns;
- options.render = render;
- options.staticRenderFns = staticRenderFns;
- }
- }
- return mount.call(this, el, hydrating)
-};
-
-/**
- * Get outerHTML of elements, taking care
- * of SVG elements in IE as well.
- */
-function getOuterHTML (el) {
- if (el.outerHTML) {
- return el.outerHTML
- } else {
- var container = document.createElement('div');
- container.appendChild(el.cloneNode(true));
- return container.innerHTML
- }
-}
-
-Vue$3.compile = compileToFunctions;
-
-return Vue$3;
-
-})));
diff --git a/vendor/assets/javascripts/vue.js.erb b/vendor/assets/javascripts/vue.js.erb
deleted file mode 100644
index 008beb10f4d..00000000000
--- a/vendor/assets/javascripts/vue.js.erb
+++ /dev/null
@@ -1,2 +0,0 @@
-<% type = Rails.env.development? ? 'full' : 'min' %>
-<%= File.read(Rails.root.join("vendor/assets/javascripts/vue.#{type}.js")) %>
diff --git a/vendor/assets/javascripts/vue.min.js b/vendor/assets/javascripts/vue.min.js
deleted file mode 100644
index f86786dd454..00000000000
--- a/vendor/assets/javascripts/vue.min.js
+++ /dev/null
@@ -1,7 +0,0 @@
-/*!
- * Vue.js v2.0.3
- * (c) 2014-2016 Evan You
- * Released under the MIT License.
- */
-!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.Vue=t()}(this,function(){"use strict";function e(e){return null==e?"":"object"==typeof e?JSON.stringify(e,null,2):String(e)}function t(e){var t=parseFloat(e,10);return t||0===t?t:e}function n(e,t){for(var n=Object.create(null),r=e.split(","),i=0;i<r.length;i++)n[r[i]]=!0;return t?function(e){return n[e.toLowerCase()]}:function(e){return n[e]}}function r(e,t){if(e.length){var n=e.indexOf(t);if(n>-1)return e.splice(n,1)}}function i(e,t){return _r.call(e,t)}function a(e){return"string"==typeof e||"number"==typeof e}function o(e){var t=Object.create(null);return function(n){var r=t[n];return r||(t[n]=e(n))}}function s(e,t){function n(n){var r=arguments.length;return r?r>1?e.apply(t,arguments):e.call(t,n):e.call(t)}return n._length=e.length,n}function c(e,t){t=t||0;for(var n=e.length-t,r=new Array(n);n--;)r[n]=e[n+t];return r}function u(e,t){for(var n in t)e[n]=t[n];return e}function l(e){return null!==e&&"object"==typeof e}function f(e){return kr.call(e)===Ar}function d(e){for(var t={},n=0;n<e.length;n++)e[n]&&u(t,e[n]);return t}function p(){}function v(e){return e.reduce(function(e,t){return e.concat(t.staticKeys||[])},[]).join(",")}function h(e,t){return e==t||!(!l(e)||!l(t))&&JSON.stringify(e)===JSON.stringify(t)}function m(e,t){for(var n=0;n<e.length;n++)if(h(e[n],t))return n;return-1}function g(e){var t=(e+"").charCodeAt(0);return 36===t||95===t}function y(e,t,n,r){Object.defineProperty(e,t,{value:n,enumerable:!!r,writable:!0,configurable:!0})}function _(e){if(!Sr.test(e)){var t=e.split(".");return function(e){for(var n=0;n<t.length;n++){if(!e)return;e=e[t[n]]}return e}}}function b(e){return/native code/.test(e.toString())}function $(e){Hr.target&&Ur.push(Hr.target),Hr.target=e}function w(){Hr.target=Ur.pop()}function C(){zr.length=0,Vr={},Jr=qr=!1}function x(){for(qr=!0,zr.sort(function(e,t){return e.id-t.id}),Kr=0;Kr<zr.length;Kr++){var e=zr[Kr],t=e.id;Vr[t]=null,e.run()}Ir&&Tr.devtools&&Ir.emit("flush"),C()}function k(e){var t=e.id;if(null==Vr[t]){if(Vr[t]=!0,qr){for(var n=zr.length-1;n>=0&&zr[n].id>e.id;)n--;zr.splice(Math.max(n,Kr)+1,0,e)}else zr.push(e);Jr||(Jr=!0,Br(x))}}function A(e,t){var n,r;t||(t=Gr,t.clear());var i=Array.isArray(e),a=l(e);if((i||a)&&Object.isExtensible(e)){if(e.__ob__){var o=e.__ob__.dep.id;if(t.has(o))return;t.add(o)}if(i)for(n=e.length;n--;)A(e[n],t);else if(a)for(r=Object.keys(e),n=r.length;n--;)A(e[r[n]],t)}}function O(e,t){e.__proto__=t}function T(e,t,n){for(var r=0,i=n.length;r<i;r++){var a=n[r];y(e,a,t[a])}}function S(e){if(l(e)){var t;return i(e,"__ob__")&&e.__ob__ instanceof ti?t=e.__ob__:ei.shouldConvert&&!Tr._isServer&&(Array.isArray(e)||f(e))&&Object.isExtensible(e)&&!e._isVue&&(t=new ti(e)),t}}function E(e,t,n,r){var i=new Hr,a=Object.getOwnPropertyDescriptor(e,t);if(!a||a.configurable!==!1){var o=a&&a.get,s=a&&a.set,c=S(n);Object.defineProperty(e,t,{enumerable:!0,configurable:!0,get:function(){var t=o?o.call(e):n;return Hr.target&&(i.depend(),c&&c.dep.depend(),Array.isArray(t)&&N(t)),t},set:function(t){var r=o?o.call(e):n;t!==r&&(s?s.call(e,t):n=t,c=S(t),i.notify())}})}}function j(e,t,n){if(Array.isArray(e))return e.splice(t,1,n),n;if(i(e,t))return void(e[t]=n);var r=e.__ob__;if(!(e._isVue||r&&r.vmCount))return r?(E(r.value,t,n),r.dep.notify(),n):void(e[t]=n)}function L(e,t){var n=e.__ob__;e._isVue||n&&n.vmCount||i(e,t)&&(delete e[t],n&&n.dep.notify())}function N(e){for(var t=void 0,n=0,r=e.length;n<r;n++)t=e[n],t&&t.__ob__&&t.__ob__.dep.depend(),Array.isArray(t)&&N(t)}function D(e){e._watchers=[],M(e),P(e),R(e),B(e),F(e)}function M(e){var t=e.$options.props;if(t){var n=e.$options.propsData||{},r=e.$options._propKeys=Object.keys(t),i=!e.$parent;ei.shouldConvert=i;for(var a=function(i){var a=r[i];E(e,a,Le(a,t,n,e))},o=0;o<r.length;o++)a(o);ei.shouldConvert=!0}}function P(e){var t=e.$options.data;t=e._data="function"==typeof t?t.call(e):t||{},f(t)||(t={});for(var n=Object.keys(t),r=e.$options.props,a=n.length;a--;)r&&i(r,n[a])||z(e,n[a]);S(t),t.__ob__&&t.__ob__.vmCount++}function R(e){var t=e.$options.computed;if(t)for(var n in t){var r=t[n];"function"==typeof r?(ni.get=I(r,e),ni.set=p):(ni.get=r.get?r.cache!==!1?I(r.get,e):s(r.get,e):p,ni.set=r.set?s(r.set,e):p),Object.defineProperty(e,n,ni)}}function I(e,t){var n=new Zr(t,e,p,{lazy:!0});return function(){return n.dirty&&n.evaluate(),Hr.target&&n.depend(),n.value}}function B(e){var t=e.$options.methods;if(t)for(var n in t)e[n]=null==t[n]?p:s(t[n],e)}function F(e){var t=e.$options.watch;if(t)for(var n in t){var r=t[n];if(Array.isArray(r))for(var i=0;i<r.length;i++)H(e,n,r[i]);else H(e,n,r)}}function H(e,t,n){var r;f(n)&&(r=n,n=n.handler),"string"==typeof n&&(n=e[n]),e.$watch(t,n,r)}function U(e){var t={};t.get=function(){return this._data},Object.defineProperty(e.prototype,"$data",t),e.prototype.$set=j,e.prototype.$delete=L,e.prototype.$watch=function(e,t,n){var r=this;n=n||{},n.user=!0;var i=new Zr(r,e,t,n);return n.immediate&&t.call(r,i.value),function(){i.teardown()}}}function z(e,t){g(t)||Object.defineProperty(e,t,{configurable:!0,enumerable:!0,get:function(){return e._data[t]},set:function(n){e._data[t]=n}})}function V(e){var t=new ri(e.tag,e.data,e.children,e.text,e.elm,e.ns,e.context,e.componentOptions);return t.isStatic=e.isStatic,t.key=e.key,t.isCloned=!0,t}function J(e){for(var t=new Array(e.length),n=0;n<e.length;n++)t[n]=V(e[n]);return t}function q(e,t,n,r){r+=t;var i=e.__injected||(e.__injected={});if(!i[r]){i[r]=!0;var a=e[t];a?e[t]=function(){a.apply(this,arguments),n.apply(this,arguments)}:e[t]=n}}function K(e,t,n,r,i){var a,o,s,c,u,l;for(a in e)if(o=e[a],s=t[a],o)if(s){if(o!==s)if(Array.isArray(s)){s.length=o.length;for(var f=0;f<s.length;f++)s[f]=o[f];e[a]=s}else s.fn=o,e[a]=s}else l="!"===a.charAt(0),u=l?a.slice(1):a,Array.isArray(o)?n(u,o.invoker=W(o),l):(o.invoker||(c=o,o=e[a]={},o.fn=c,o.invoker=Z(o)),n(u,o.invoker,l));else;for(a in t)e[a]||(u="!"===a.charAt(0)?a.slice(1):a,r(u,t[a].invoker))}function W(e){return function(t){for(var n=arguments,r=1===arguments.length,i=0;i<e.length;i++)r?e[i](t):e[i].apply(null,n)}}function Z(e){return function(t){var n=1===arguments.length;n?e.fn(t):e.fn.apply(null,arguments)}}function G(e,t,n){if(a(e))return[Y(e)];if(Array.isArray(e)){for(var r=[],i=0,o=e.length;i<o;i++){var s=e[i],c=r[r.length-1];Array.isArray(s)?r.push.apply(r,G(s,t,(n||"")+"_"+i)):a(s)?c&&c.text?c.text+=String(s):""!==s&&r.push(Y(s)):s instanceof ri&&(s.text&&c&&c.text?c.text+=s.text:(t&&Q(s,t),s.tag&&null==s.key&&null!=n&&(s.key="__vlist"+n+"_"+i+"__"),r.push(s)))}return r}}function Y(e){return new ri(void 0,void 0,void 0,String(e))}function Q(e,t){if(e.tag&&!e.ns&&(e.ns=t,e.children))for(var n=0,r=e.children.length;n<r;n++)Q(e.children[n],t)}function X(e){return e&&e.filter(function(e){return e&&e.componentOptions})[0]}function ee(e){var t=e.$options,n=t.parent;if(n&&!t.abstract){for(;n.$options.abstract&&n.$parent;)n=n.$parent;n.$children.push(e)}e.$parent=n,e.$root=n?n.$root:e,e.$children=[],e.$refs={},e._watcher=null,e._inactive=!1,e._isMounted=!1,e._isDestroyed=!1,e._isBeingDestroyed=!1}function te(e){e.prototype._mount=function(e,t){var n=this;return n.$el=e,n.$options.render||(n.$options.render=ii),ne(n,"beforeMount"),n._watcher=new Zr(n,function(){n._update(n._render(),t)},p),t=!1,null==n.$vnode&&(n._isMounted=!0,ne(n,"mounted")),n},e.prototype._update=function(e,t){var n=this;n._isMounted&&ne(n,"beforeUpdate");var r=n.$el,i=ai;ai=n;var a=n._vnode;n._vnode=e,a?n.$el=n.__patch__(a,e):n.$el=n.__patch__(n.$el,e,t),ai=i,r&&(r.__vue__=null),n.$el&&(n.$el.__vue__=n),n.$vnode&&n.$parent&&n.$vnode===n.$parent._vnode&&(n.$parent.$el=n.$el),n._isMounted&&ne(n,"updated")},e.prototype._updateFromParent=function(e,t,n,r){var i=this,a=!(!i.$options._renderChildren&&!r);if(i.$options._parentVnode=n,i.$options._renderChildren=r,e&&i.$options.props){ei.shouldConvert=!1;for(var o=i.$options._propKeys||[],s=0;s<o.length;s++){var c=o[s];i[c]=Le(c,i.$options.props,e,i)}ei.shouldConvert=!0}if(t){var u=i.$options._parentListeners;i.$options._parentListeners=t,i._updateListeners(t,u)}a&&(i.$slots=_e(r,i._renderContext),i.$forceUpdate())},e.prototype.$forceUpdate=function(){var e=this;e._watcher&&e._watcher.update()},e.prototype.$destroy=function(){var e=this;if(!e._isBeingDestroyed){ne(e,"beforeDestroy"),e._isBeingDestroyed=!0;var t=e.$parent;!t||t._isBeingDestroyed||e.$options.abstract||r(t.$children,e),e._watcher&&e._watcher.teardown();for(var n=e._watchers.length;n--;)e._watchers[n].teardown();e._data.__ob__&&e._data.__ob__.vmCount--,e._isDestroyed=!0,ne(e,"destroyed"),e.$off(),e.$el&&(e.$el.__vue__=null),e.__patch__(e._vnode,null)}}}function ne(e,t){var n=e.$options[t];if(n)for(var r=0,i=n.length;r<i;r++)n[r].call(e);e.$emit("hook:"+t)}function re(e,t,n,r,i){if(e&&(l(e)&&(e=Ce.extend(e)),"function"==typeof e)){if(!e.cid)if(e.resolved)e=e.resolved;else if(e=le(e,function(){n.$forceUpdate()}),!e)return;t=t||{};var a=fe(t,e);if(e.options.functional)return ie(e,a,t,n,r);var o=t.on;t.on=t.nativeOn,e.options.abstract&&(t={}),pe(t);var s=e.options.name||i,c=new ri("vue-component-"+e.cid+(s?"-"+s:""),t,void 0,void 0,void 0,void 0,n,{Ctor:e,propsData:a,listeners:o,tag:i,children:r});return c}}function ie(e,t,n,r,i){var a={},o=e.options.props;if(o)for(var c in o)a[c]=Le(c,o,t);var u=e.options.render.call(null,s(he,{_self:Object.create(r)}),{props:a,data:n,parent:r,children:G(i),slots:function(){return _e(i,r)}});return u instanceof ri&&(u.functionalContext=r,n.slot&&((u.data||(u.data={})).slot=n.slot)),u}function ae(e,t){var n=e.componentOptions,r={_isComponent:!0,parent:t,propsData:n.propsData,_componentTag:n.tag,_parentVnode:e,_parentListeners:n.listeners,_renderChildren:n.children},i=e.data.inlineTemplate;return i&&(r.render=i.render,r.staticRenderFns=i.staticRenderFns),new n.Ctor(r)}function oe(e,t){if(!e.child||e.child._isDestroyed){var n=e.child=ae(e,ai);n.$mount(t?e.elm:void 0,t)}}function se(e,t){var n=t.componentOptions,r=t.child=e.child;r._updateFromParent(n.propsData,n.listeners,t,n.children)}function ce(e){e.child._isMounted||(e.child._isMounted=!0,ne(e.child,"mounted")),e.data.keepAlive&&(e.child._inactive=!1,ne(e.child,"activated"))}function ue(e){e.child._isDestroyed||(e.data.keepAlive?(e.child._inactive=!0,ne(e.child,"deactivated")):e.child.$destroy())}function le(e,t){if(!e.requested){e.requested=!0;var n=e.pendingCallbacks=[t],r=!0,i=function(t){if(l(t)&&(t=Ce.extend(t)),e.resolved=t,!r)for(var i=0,a=n.length;i<a;i++)n[i](t)},a=function(e){},o=e(i,a);return o&&"function"==typeof o.then&&!e.resolved&&o.then(i,a),r=!1,e.resolved}e.pendingCallbacks.push(t)}function fe(e,t){var n=t.options.props;if(n){var r={},i=e.attrs,a=e.props,o=e.domProps;if(i||a||o)for(var s in n){var c=xr(s);de(r,a,s,c,!0)||de(r,i,s,c)||de(r,o,s,c)}return r}}function de(e,t,n,r,a){if(t){if(i(t,n))return e[n]=t[n],a||delete t[n],!0;if(i(t,r))return e[n]=t[r],a||delete t[r],!0}return!1}function pe(e){e.hook||(e.hook={});for(var t=0;t<si.length;t++){var n=si[t],r=e.hook[n],i=oi[n];e.hook[n]=r?ve(i,r):i}}function ve(e,t){return function(n,r){e(n,r),t(n,r)}}function he(e,t,n){return t&&(Array.isArray(t)||"object"!=typeof t)&&(n=t,t=void 0),me(this._self,e,t,n)}function me(e,t,n,r){if(!n||!n.__ob__){if(!t)return ii();if("string"==typeof t){var i,a=Tr.getTagNamespace(t);return Tr.isReservedTag(t)?new ri(t,n,G(r,a),void 0,void 0,a,e):(i=je(e.$options,"components",t))?re(i,n,e,r,t):new ri(t,n,G(r,a),void 0,void 0,a,e)}return re(t,n,e,r)}}function ge(e){e.$vnode=null,e._vnode=null,e._staticTrees=null,e._renderContext=e.$options._parentVnode&&e.$options._parentVnode.context,e.$slots=_e(e.$options._renderChildren,e._renderContext),e.$createElement=s(he,e),e.$options.el&&e.$mount(e.$options.el)}function ye(n){n.prototype.$nextTick=function(e){Br(e,this)},n.prototype._render=function(){var e=this,t=e.$options,n=t.render,r=t.staticRenderFns,i=t._parentVnode;if(e._isMounted)for(var a in e.$slots)e.$slots[a]=J(e.$slots[a]);r&&!e._staticTrees&&(e._staticTrees=[]),e.$vnode=i;var o;try{o=n.call(e._renderProxy,e.$createElement)}catch(t){if(Tr.errorHandler)Tr.errorHandler.call(null,t,e);else{if(Tr._isServer)throw t;setTimeout(function(){throw t},0)}o=e._vnode}return o instanceof ri||(o=ii()),o.parent=i,o},n.prototype._h=he,n.prototype._s=e,n.prototype._n=t,n.prototype._e=ii,n.prototype._q=h,n.prototype._i=m,n.prototype._m=function(e,t){var n=this._staticTrees[e];if(n&&!t)return Array.isArray(n)?J(n):V(n);if(n=this._staticTrees[e]=this.$options.staticRenderFns[e].call(this._renderProxy),Array.isArray(n))for(var r=0;r<n.length;r++)"string"!=typeof n[r]&&(n[r].isStatic=!0,n[r].key="__static__"+e+"_"+r);else n.isStatic=!0,n.key="__static__"+e;return n};var r=function(e){return e};n.prototype._f=function(e){return je(this.$options,"filters",e,!0)||r},n.prototype._l=function(e,t){var n,r,i,a,o;if(Array.isArray(e))for(n=new Array(e.length),r=0,i=e.length;r<i;r++)n[r]=t(e[r],r);else if("number"==typeof e)for(n=new Array(e),r=0;r<e;r++)n[r]=t(r+1,r);else if(l(e))for(a=Object.keys(e),n=new Array(a.length),r=0,i=a.length;r<i;r++)o=a[r],n[r]=t(e[o],o,r);return n},n.prototype._t=function(e,t){var n=this.$slots[e];return n||t},n.prototype._b=function(e,t,n){if(t)if(l(t)){Array.isArray(t)&&(t=d(t));for(var r in t)if("class"===r||"style"===r)e[r]=t[r];else{var i=n||Tr.mustUseProp(r)?e.domProps||(e.domProps={}):e.attrs||(e.attrs={});i[r]=t[r]}}else;return e},n.prototype._k=function(e){return Tr.keyCodes[e]}}function _e(e,t){var n={};if(!e)return n;for(var r,i,a=G(e)||[],o=[],s=0,c=a.length;s<c;s++)if(i=a[s],(i.context===t||i.functionalContext===t)&&i.data&&(r=i.data.slot)){var u=n[r]||(n[r]=[]);"template"===i.tag?u.push.apply(u,i.children):u.push(i)}else o.push(i);return o.length&&(1!==o.length||" "!==o[0].text&&!o[0].isComment)&&(n.default=o),n}function be(e){e._events=Object.create(null);var t=e.$options._parentListeners,n=s(e.$on,e),r=s(e.$off,e);e._updateListeners=function(t,i){K(t,i||{},n,r,e)},t&&e._updateListeners(t)}function $e(e){e.prototype.$on=function(e,t){var n=this;return(n._events[e]||(n._events[e]=[])).push(t),n},e.prototype.$once=function(e,t){function n(){r.$off(e,n),t.apply(r,arguments)}var r=this;return n.fn=t,r.$on(e,n),r},e.prototype.$off=function(e,t){var n=this;if(!arguments.length)return n._events=Object.create(null),n;var r=n._events[e];if(!r)return n;if(1===arguments.length)return n._events[e]=null,n;for(var i,a=r.length;a--;)if(i=r[a],i===t||i.fn===t){r.splice(a,1);break}return n},e.prototype.$emit=function(e){var t=this,n=t._events[e];if(n){n=n.length>1?c(n):n;for(var r=c(arguments,1),i=0,a=n.length;i<a;i++)n[i].apply(t,r)}return t}}function we(e){function t(e,t){var r=e.$options=Object.create(n(e));r.parent=t.parent,r.propsData=t.propsData,r._parentVnode=t._parentVnode,r._parentListeners=t._parentListeners,r._renderChildren=t._renderChildren,r._componentTag=t._componentTag,t.render&&(r.render=t.render,r.staticRenderFns=t.staticRenderFns)}function n(e){var t=e.constructor,n=t.options;if(t.super){var r=t.super.options,i=t.superOptions;r!==i&&(t.superOptions=r,n=t.options=Ee(r,t.extendOptions),n.name&&(n.components[n.name]=t))}return n}e.prototype._init=function(e){var r=this;r._uid=ci++,r._isVue=!0,e&&e._isComponent?t(r,e):r.$options=Ee(n(r),e||{},r),r._renderProxy=r,r._self=r,ee(r),be(r),ne(r,"beforeCreate"),D(r),ne(r,"created"),ge(r)}}function Ce(e){this._init(e)}function xe(e,t){var n,r,a;for(n in t)r=e[n],a=t[n],i(e,n)?l(r)&&l(a)&&xe(r,a):j(e,n,a);return e}function ke(e,t){return t?e?e.concat(t):Array.isArray(t)?t:[t]:e}function Ae(e,t){var n=Object.create(e||null);return t?u(n,t):n}function Oe(e){if(e.components){var t,n=e.components;for(var r in n){var i=r.toLowerCase();yr(i)||Tr.isReservedTag(i)||(t=n[r],f(t)&&(n[r]=Ce.extend(t)))}}}function Te(e){var t=e.props;if(t){var n,r,i,a={};if(Array.isArray(t))for(n=t.length;n--;)r=t[n],"string"==typeof r&&(i=$r(r),a[i]={type:null});else if(f(t))for(var o in t)r=t[o],i=$r(o),a[i]=f(r)?r:{type:r};e.props=a}}function Se(e){var t=e.directives;if(t)for(var n in t){var r=t[n];"function"==typeof r&&(t[n]={bind:r,update:r})}}function Ee(e,t,n){function r(r){var i=fi[r]||di;l[r]=i(e[r],t[r],n,r)}Oe(t),Te(t),Se(t);var a=t.extends;if(a&&(e="function"==typeof a?Ee(e,a.options,n):Ee(e,a,n)),t.mixins)for(var o=0,s=t.mixins.length;o<s;o++){var c=t.mixins[o];c.prototype instanceof Ce&&(c=c.options),e=Ee(e,c,n)}var u,l={};for(u in e)r(u);for(u in t)i(e,u)||r(u);return l}function je(e,t,n,r){if("string"==typeof n){var i=e[t],a=i[n]||i[$r(n)]||i[wr($r(n))];return a}}function Le(e,t,n,r){var a=t[e],o=!i(n,e),s=n[e];if(Me(a.type)&&(o&&!i(a,"default")?s=!1:""!==s&&s!==xr(e)||(s=!0)),void 0===s){s=Ne(r,a,e);var c=ei.shouldConvert;ei.shouldConvert=!0,S(s),ei.shouldConvert=c}return s}function Ne(e,t,n){if(i(t,"default")){var r=t.default;return l(r),"function"==typeof r&&t.type!==Function?r.call(e):r}}function De(e){var t=e&&e.toString().match(/^\s*function (\w+)/);return t&&t[1]}function Me(e){if(!Array.isArray(e))return"Boolean"===De(e);for(var t=0,n=e.length;t<n;t++)if("Boolean"===De(e[t]))return!0;return!1}function Pe(e){e.use=function(e){if(!e.installed){var t=c(arguments,1);return t.unshift(this),"function"==typeof e.install?e.install.apply(e,t):e.apply(null,t),e.installed=!0,this}}}function Re(e){e.mixin=function(t){e.options=Ee(e.options,t)}}function Ie(e){e.cid=0;var t=1;e.extend=function(e){e=e||{};var n=this,r=0===n.cid;if(r&&e._Ctor)return e._Ctor;var i=e.name||n.options.name,a=function(e){this._init(e)};return a.prototype=Object.create(n.prototype),a.prototype.constructor=a,a.cid=t++,a.options=Ee(n.options,e),a.super=n,a.extend=n.extend,Tr._assetTypes.forEach(function(e){a[e]=n[e]}),i&&(a.options.components[i]=a),a.superOptions=n.options,a.extendOptions=e,r&&(e._Ctor=a),a}}function Be(e){Tr._assetTypes.forEach(function(t){e[t]=function(n,r){return r?("component"===t&&f(r)&&(r.name=r.name||n,r=e.extend(r)),"directive"===t&&"function"==typeof r&&(r={bind:r,update:r}),this.options[t+"s"][n]=r,r):this.options[t+"s"][n]}})}function Fe(e){var t={};t.get=function(){return Tr},Object.defineProperty(e,"config",t),e.util=pi,e.set=j,e.delete=L,e.nextTick=Br,e.options=Object.create(null),Tr._assetTypes.forEach(function(t){e.options[t+"s"]=Object.create(null)}),u(e.options.components,hi),Pe(e),Re(e),Ie(e),Be(e)}function He(e){for(var t=e.data,n=e,r=e;r.child;)r=r.child._vnode,r.data&&(t=Ue(r.data,t));for(;n=n.parent;)n.data&&(t=Ue(t,n.data));return ze(t)}function Ue(e,t){return{staticClass:Ve(e.staticClass,t.staticClass),class:e.class?[e.class,t.class]:t.class}}function ze(e){var t=e.class,n=e.staticClass;return n||t?Ve(n,Je(t)):""}function Ve(e,t){return e?t?e+" "+t:e:t||""}function Je(e){var t="";if(!e)return t;if("string"==typeof e)return e;if(Array.isArray(e)){for(var n,r=0,i=e.length;r<i;r++)e[r]&&(n=Je(e[r]))&&(t+=n+" ");return t.slice(0,-1)}if(l(e)){for(var a in e)e[a]&&(t+=a+" ");return t.slice(0,-1)}return t}function qe(e){return Si(e)?"svg":"math"===e?"math":void 0}function Ke(e){if(!jr)return!0;if(ji(e))return!1;if(e=e.toLowerCase(),null!=Li[e])return Li[e];var t=document.createElement(e);return e.indexOf("-")>-1?Li[e]=t.constructor===window.HTMLUnknownElement||t.constructor===window.HTMLElement:Li[e]=/HTMLUnknownElement/.test(t.toString())}function We(e){if("string"==typeof e){if(e=document.querySelector(e),!e)return document.createElement("div")}return e}function Ze(e,t){var n=document.createElement(e);return"select"!==e?n:(t.data&&t.data.attrs&&"multiple"in t.data.attrs&&n.setAttribute("multiple","multiple"),n)}function Ge(e,t){return document.createElementNS(xi[e],t)}function Ye(e){return document.createTextNode(e)}function Qe(e){return document.createComment(e)}function Xe(e,t,n){e.insertBefore(t,n)}function et(e,t){e.removeChild(t)}function tt(e,t){e.appendChild(t)}function nt(e){return e.parentNode}function rt(e){return e.nextSibling}function it(e){return e.tagName}function at(e,t){e.textContent=t}function ot(e){return e.childNodes}function st(e,t,n){e.setAttribute(t,n)}function ct(e,t){var n=e.data.ref;if(n){var i=e.context,a=e.child||e.elm,o=i.$refs;t?Array.isArray(o[n])?r(o[n],a):o[n]===a&&(o[n]=void 0):e.data.refInFor?Array.isArray(o[n])?o[n].push(a):o[n]=[a]:o[n]=a}}function ut(e){return null==e}function lt(e){return null!=e}function ft(e,t){return e.key===t.key&&e.tag===t.tag&&e.isComment===t.isComment&&!e.data==!t.data}function dt(e,t,n){var r,i,a={};for(r=t;r<=n;++r)i=e[r].key,lt(i)&&(a[i]=r);return a}function pt(e){function t(e){return new ri(C.tagName(e).toLowerCase(),{},[],void 0,e)}function n(e,t){function n(){0===--n.listeners&&r(e)}return n.listeners=t,n}function r(e){var t=C.parentNode(e);C.removeChild(t,e)}function i(e,t,n){var r,i=e.data;if(e.isRootInsert=!n,lt(i)&&(lt(r=i.hook)&&lt(r=r.init)&&r(e),lt(r=e.child)))return u(e,t),e.elm;var a=e.children,s=e.tag;return lt(s)?(e.elm=e.ns?C.createElementNS(e.ns,s):C.createElement(s,e),l(e),o(e,a,t),lt(i)&&c(e,t)):e.isComment?e.elm=C.createComment(e.text):e.elm=C.createTextNode(e.text),e.elm}function o(e,t,n){if(Array.isArray(t))for(var r=0;r<t.length;++r)C.appendChild(e.elm,i(t[r],n,!0));else a(e.text)&&C.appendChild(e.elm,C.createTextNode(e.text))}function s(e){for(;e.child;)e=e.child._vnode;return lt(e.tag)}function c(e,t){for(var n=0;n<$.create.length;++n)$.create[n](Mi,e);_=e.data.hook,lt(_)&&(_.create&&_.create(Mi,e),_.insert&&t.push(e))}function u(e,t){e.data.pendingInsert&&t.push.apply(t,e.data.pendingInsert),e.elm=e.child.$el,s(e)?(c(e,t),l(e)):(ct(e),t.push(e))}function l(e){var t;lt(t=e.context)&&lt(t=t.$options._scopeId)&&C.setAttribute(e.elm,t,""),lt(t=ai)&&t!==e.context&&lt(t=t.$options._scopeId)&&C.setAttribute(e.elm,t,"")}function f(e,t,n,r,a,o){for(;r<=a;++r)C.insertBefore(e,i(n[r],o),t)}function d(e){var t,n,r=e.data;if(lt(r))for(lt(t=r.hook)&&lt(t=t.destroy)&&t(e),t=0;t<$.destroy.length;++t)$.destroy[t](e);if(lt(t=e.children))for(n=0;n<e.children.length;++n)d(e.children[n])}function p(e,t,n,r){for(;n<=r;++n){var i=t[n];lt(i)&&(lt(i.tag)?(v(i),d(i)):C.removeChild(e,i.elm))}}function v(e,t){if(t||lt(e.data)){var i=$.remove.length+1;for(t?t.listeners+=i:t=n(e.elm,i),lt(_=e.child)&&lt(_=_._vnode)&&lt(_.data)&&v(_,t),_=0;_<$.remove.length;++_)$.remove[_](e,t);lt(_=e.data.hook)&&lt(_=_.remove)?_(e,t):t()}else r(e.elm)}function h(e,t,n,r,a){for(var o,s,c,u,l=0,d=0,v=t.length-1,h=t[0],g=t[v],y=n.length-1,_=n[0],b=n[y],$=!a;l<=v&&d<=y;)ut(h)?h=t[++l]:ut(g)?g=t[--v]:ft(h,_)?(m(h,_,r),h=t[++l],_=n[++d]):ft(g,b)?(m(g,b,r),g=t[--v],b=n[--y]):ft(h,b)?(m(h,b,r),$&&C.insertBefore(e,h.elm,C.nextSibling(g.elm)),h=t[++l],b=n[--y]):ft(g,_)?(m(g,_,r),$&&C.insertBefore(e,g.elm,h.elm),g=t[--v],_=n[++d]):(ut(o)&&(o=dt(t,l,v)),s=lt(_.key)?o[_.key]:null,ut(s)?(C.insertBefore(e,i(_,r),h.elm),_=n[++d]):(c=t[s],c.tag!==_.tag?(C.insertBefore(e,i(_,r),h.elm),_=n[++d]):(m(c,_,r),t[s]=void 0,$&&C.insertBefore(e,_.elm,h.elm),_=n[++d])));l>v?(u=ut(n[y+1])?null:n[y+1].elm,f(e,u,n,d,y,r)):d>y&&p(e,t,l,v)}function m(e,t,n,r){if(e!==t){if(t.isStatic&&e.isStatic&&t.key===e.key&&t.isCloned)return void(t.elm=e.elm);var i,a=t.data,o=lt(a);o&&lt(i=a.hook)&&lt(i=i.prepatch)&&i(e,t);var c=t.elm=e.elm,u=e.children,l=t.children;if(o&&s(t)){for(i=0;i<$.update.length;++i)$.update[i](e,t);lt(i=a.hook)&&lt(i=i.update)&&i(e,t)}ut(t.text)?lt(u)&&lt(l)?u!==l&&h(c,u,l,n,r):lt(l)?(lt(e.text)&&C.setTextContent(c,""),f(c,null,l,0,l.length-1,n)):lt(u)?p(c,u,0,u.length-1):lt(e.text)&&C.setTextContent(c,""):e.text!==t.text&&C.setTextContent(c,t.text),o&&lt(i=a.hook)&&lt(i=i.postpatch)&&i(e,t)}}function g(e,t,n){if(n&&e.parent)e.parent.data.pendingInsert=t;else for(var r=0;r<t.length;++r)t[r].data.hook.insert(t[r])}function y(e,t,n){t.elm=e;var r=t.tag,i=t.data,a=t.children;if(lt(i)&&(lt(_=i.hook)&&lt(_=_.init)&&_(t,!0),lt(_=t.child)))return u(t,n),!0;if(lt(r)){if(lt(a)){var s=C.childNodes(e);if(s.length){var l=!0;if(s.length!==a.length)l=!1;else for(var f=0;f<a.length;f++)if(!y(s[f],a[f],n)){l=!1;break}if(!l)return!1}else o(t,a,n)}lt(i)&&c(t,n)}return!0}var _,b,$={},w=e.modules,C=e.nodeOps;for(_=0;_<Pi.length;++_)for($[Pi[_]]=[],b=0;b<w.length;++b)void 0!==w[b][Pi[_]]&&$[Pi[_]].push(w[b][Pi[_]]);return function(e,n,r,a){if(!n)return void(e&&d(e));var o,c,u=!1,l=[];if(e){var f=lt(e.nodeType);if(!f&&ft(e,n))m(e,n,l,a);else{if(f){if(1===e.nodeType&&e.hasAttribute("server-rendered")&&(e.removeAttribute("server-rendered"),r=!0),r&&y(e,n,l))return g(n,l,!0),e;e=t(e)}if(o=e.elm,c=C.parentNode(o),i(n,l),n.parent&&(n.parent.elm=n.elm,s(n)))for(var v=0;v<$.create.length;++v)$.create[v](Mi,n.parent);null!==c?(C.insertBefore(c,n.elm,C.nextSibling(o)),p(c,[e],0,0)):lt(e.tag)&&d(e)}}else u=!0,i(n,l);return g(n,l,u),n.elm}}function vt(e,t){if(e.data.directives||t.data.directives){var n,r,i,a=e===Mi,o=ht(e.data.directives,e.context),s=ht(t.data.directives,t.context),c=[],u=[];for(n in s)r=o[n],i=s[n],r?(i.oldValue=r.value,gt(i,"update",t,e),i.def&&i.def.componentUpdated&&u.push(i)):(gt(i,"bind",t,e),i.def&&i.def.inserted&&c.push(i));if(c.length){var l=function(){c.forEach(function(n){gt(n,"inserted",t,e)})};a?q(t.data.hook||(t.data.hook={}),"insert",l,"dir-insert"):l()}if(u.length&&q(t.data.hook||(t.data.hook={}),"postpatch",function(){u.forEach(function(n){gt(n,"componentUpdated",t,e)})},"dir-postpatch"),!a)for(n in o)s[n]||gt(o[n],"unbind",e)}}function ht(e,t){var n=Object.create(null);if(!e)return n;var r,i;for(r=0;r<e.length;r++)i=e[r],i.modifiers||(i.modifiers=Ii),n[mt(i)]=i,i.def=je(t.$options,"directives",i.name,!0);return n}function mt(e){return e.rawName||e.name+"."+Object.keys(e.modifiers||{}).join(".")}function gt(e,t,n,r){var i=e.def&&e.def[t];i&&i(n.elm,e,n,r)}function yt(e,t){if(e.data.attrs||t.data.attrs){var n,r,i,a=t.elm,o=e.data.attrs||{},s=t.data.attrs||{};s.__ob__&&(s=t.data.attrs=u({},s));for(n in s)r=s[n],i=o[n],i!==r&&_t(a,n,r);for(n in o)null==s[n]&&($i(n)?a.removeAttributeNS(bi,wi(n)):yi(n)||a.removeAttribute(n))}}function _t(e,t,n){_i(t)?Ci(n)?e.removeAttribute(t):e.setAttribute(t,t):yi(t)?e.setAttribute(t,Ci(n)||"false"===n?"false":"true"):$i(t)?Ci(n)?e.removeAttributeNS(bi,wi(t)):e.setAttributeNS(bi,t,n):Ci(n)?e.removeAttribute(t):e.setAttribute(t,n)}function bt(e,t){var n=t.elm,r=t.data,i=e.data;if(r.staticClass||r.class||i&&(i.staticClass||i.class)){var a=He(t),o=n._transitionClasses;o&&(a=Ve(a,Je(o))),a!==n._prevClass&&(n.setAttribute("class",a),n._prevClass=a)}}function $t(e,t){if(e.data.on||t.data.on){var n=t.data.on||{},r=e.data.on||{},i=t.elm._v_add||(t.elm._v_add=function(e,n,r){t.elm.addEventListener(e,n,r)}),a=t.elm._v_remove||(t.elm._v_remove=function(e,n){t.elm.removeEventListener(e,n)});K(n,r,i,a,t.context)}}function wt(e,t){if(e.data.domProps||t.data.domProps){var n,r,i=t.elm,a=e.data.domProps||{},o=t.data.domProps||{};o.__ob__&&(o=t.data.domProps=u({},o));for(n in a)null==o[n]&&(i[n]=void 0);for(n in o)if("textContent"!==n&&"innerHTML"!==n||!t.children||(t.children.length=0),r=o[n],"value"===n){i._value=r;var s=null==r?"":String(r);i.value===s||i.composing||(i.value=s)}else i[n]=r}}function Ct(e,t){if(e.data&&e.data.style||t.data.style){var n,r,i=t.elm,a=e.data.style||{},o=t.data.style||{};if("string"==typeof o)return void(i.style.cssText=o);var s=o.__ob__;Array.isArray(o)&&(o=t.data.style=d(o)),s&&(o=t.data.style=u({},o));for(r in a)null==o[r]&&(i.style[Ji(r)]="");for(r in o)n=o[r],n!==a[r]&&(i.style[Ji(r)]=null==n?"":n)}}function xt(e,t){if(e.classList)t.indexOf(" ")>-1?t.split(/\s+/).forEach(function(t){return e.classList.add(t)}):e.classList.add(t);else{var n=" "+e.getAttribute("class")+" ";n.indexOf(" "+t+" ")<0&&e.setAttribute("class",(n+t).trim())}}function kt(e,t){if(e.classList)t.indexOf(" ")>-1?t.split(/\s+/).forEach(function(t){return e.classList.remove(t)}):e.classList.remove(t);else{for(var n=" "+e.getAttribute("class")+" ",r=" "+t+" ";n.indexOf(r)>=0;)n=n.replace(r," ");e.setAttribute("class",n.trim())}}function At(e){ea(function(){ea(e)})}function Ot(e,t){(e._transitionClasses||(e._transitionClasses=[])).push(t),xt(e,t)}function Tt(e,t){e._transitionClasses&&r(e._transitionClasses,t),kt(e,t)}function St(e,t,n){var r=Et(e,t),i=r.type,a=r.timeout,o=r.propCount;if(!i)return n();var s=i===Wi?Yi:Xi,c=0,u=function(){e.removeEventListener(s,l),n()},l=function(t){t.target===e&&++c>=o&&u()};setTimeout(function(){c<o&&u()},a+1),e.addEventListener(s,l)}function Et(e,t){var n,r=window.getComputedStyle(e),i=r[Gi+"Delay"].split(", "),a=r[Gi+"Duration"].split(", "),o=jt(i,a),s=r[Qi+"Delay"].split(", "),c=r[Qi+"Duration"].split(", "),u=jt(s,c),l=0,f=0;t===Wi?o>0&&(n=Wi,l=o,f=a.length):t===Zi?u>0&&(n=Zi,l=u,f=c.length):(l=Math.max(o,u),n=l>0?o>u?Wi:Zi:null,f=n?n===Wi?a.length:c.length:0);var d=n===Wi&&ta.test(r[Gi+"Property"]);return{type:n,timeout:l,propCount:f,hasTransform:d}}function jt(e,t){return Math.max.apply(null,t.map(function(t,n){return Lt(t)+Lt(e[n])}))}function Lt(e){return 1e3*Number(e.slice(0,-1))}function Nt(e){var t=e.elm;t._leaveCb&&(t._leaveCb.cancelled=!0,t._leaveCb());var n=Mt(e.data.transition);if(n&&!t._enterCb&&1===t.nodeType){var r=n.css,i=n.type,a=n.enterClass,o=n.enterActiveClass,s=n.appearClass,c=n.appearActiveClass,u=n.beforeEnter,l=n.enter,f=n.afterEnter,d=n.enterCancelled,p=n.beforeAppear,v=n.appear,h=n.afterAppear,m=n.appearCancelled,g=ai.$vnode,y=g&&g.parent?g.parent.context:ai,_=!y._isMounted||!e.isRootInsert;if(!_||v||""===v){var b=_?s:a,$=_?c:o,w=_?p||u:u,C=_&&"function"==typeof v?v:l,x=_?h||f:f,k=_?m||d:d,A=r!==!1&&!Dr,O=C&&(C._length||C.length)>1,T=t._enterCb=Pt(function(){A&&Tt(t,$),T.cancelled?(A&&Tt(t,b),k&&k(t)):x&&x(t),t._enterCb=null});e.data.show||q(e.data.hook||(e.data.hook={}),"insert",function(){var n=t.parentNode,r=n&&n._pending&&n._pending[e.key];r&&r.tag===e.tag&&r.elm._leaveCb&&r.elm._leaveCb(),C&&C(t,T)},"transition-insert"),w&&w(t),A&&(Ot(t,b),Ot(t,$),At(function(){Tt(t,b),T.cancelled||O||St(t,i,T)})),e.data.show&&C&&C(t,T),A||O||T()}}}function Dt(e,t){function n(){m.cancelled||(e.data.show||((r.parentNode._pending||(r.parentNode._pending={}))[e.key]=e),u&&u(r),v&&(Ot(r,s),Ot(r,c),At(function(){Tt(r,s),m.cancelled||h||St(r,o,m)})),l&&l(r,m),v||h||m())}var r=e.elm;r._enterCb&&(r._enterCb.cancelled=!0,r._enterCb());var i=Mt(e.data.transition);if(!i)return t();if(!r._leaveCb&&1===r.nodeType){var a=i.css,o=i.type,s=i.leaveClass,c=i.leaveActiveClass,u=i.beforeLeave,l=i.leave,f=i.afterLeave,d=i.leaveCancelled,p=i.delayLeave,v=a!==!1&&!Dr,h=l&&(l._length||l.length)>1,m=r._leaveCb=Pt(function(){r.parentNode&&r.parentNode._pending&&(r.parentNode._pending[e.key]=null),v&&Tt(r,c),m.cancelled?(v&&Tt(r,s),d&&d(r)):(t(),f&&f(r)),r._leaveCb=null});p?p(n):n()}}function Mt(e){if(e){if("object"==typeof e){var t={};return e.css!==!1&&u(t,na(e.name||"v")),u(t,e),t}return"string"==typeof e?na(e):void 0}}function Pt(e){var t=!1;return function(){t||(t=!0,e())}}function Rt(e,t,n){var r=t.value,i=e.multiple;if(!i||Array.isArray(r)){for(var a,o,s=0,c=e.options.length;s<c;s++)if(o=e.options[s],i)a=m(r,Bt(o))>-1,o.selected!==a&&(o.selected=a);else if(h(Bt(o),r))return void(e.selectedIndex!==s&&(e.selectedIndex=s));i||(e.selectedIndex=-1)}}function It(e,t){for(var n=0,r=t.length;n<r;n++)if(h(Bt(t[n]),e))return!1;return!0}function Bt(e){return"_value"in e?e._value:e.value}function Ft(e){e.target.composing=!0}function Ht(e){e.target.composing=!1,Ut(e.target,"input")}function Ut(e,t){var n=document.createEvent("HTMLEvents");n.initEvent(t,!0,!0),e.dispatchEvent(n)}function zt(e){return!e.child||e.data&&e.data.transition?e:zt(e.child._vnode)}function Vt(e){var t=e&&e.componentOptions;return t&&t.Ctor.options.abstract?Vt(X(t.children)):e}function Jt(e){var t={},n=e.$options;for(var r in n.propsData)t[r]=e[r];var i=n._parentListeners;for(var a in i)t[$r(a)]=i[a].fn;return t}function qt(e,t){return/\d-keep-alive$/.test(t.tag)?e("keep-alive"):null}function Kt(e){for(;e=e.parent;)if(e.data.transition)return!0}function Wt(e){e.elm._moveCb&&e.elm._moveCb(),e.elm._enterCb&&e.elm._enterCb();
-}function Zt(e){e.data.newPos=e.elm.getBoundingClientRect()}function Gt(e){var t=e.data.pos,n=e.data.newPos,r=t.left-n.left,i=t.top-n.top;if(r||i){e.data.moved=!0;var a=e.elm.style;a.transform=a.WebkitTransform="translate("+r+"px,"+i+"px)",a.transitionDuration="0s"}}function Yt(e,t){var n=document.createElement("div");return n.innerHTML='<div a="'+e+'">',n.innerHTML.indexOf(t)>0}function Qt(e){return ma.innerHTML=e,ma.textContent}function Xt(e,t){return t&&(e=e.replace(Za,"\n")),e.replace(Ka,"<").replace(Wa,">").replace(Ga,"&").replace(Ya,'"')}function en(e,t){function n(t){f+=t,e=e.substring(t)}function r(){var t=e.match(Ca);if(t){var r={tagName:t[1],attrs:[],start:f};n(t[0].length);for(var i,a;!(i=e.match(xa))&&(a=e.match(ba));)n(a[0].length),r.attrs.push(a);if(i)return r.unarySlash=i[1],n(i[0].length),r.end=f,r}}function i(e){var n=e.tagName,r=e.unarySlash;u&&("p"===s&&Ti(n)&&a("",s),Oi(n)&&s===n&&a("",n));for(var i=l(n)||"html"===n&&"head"===s||!!r,o=e.attrs.length,f=new Array(o),d=0;d<o;d++){var p=e.attrs[d];Oa&&p[0].indexOf('""')===-1&&(""===p[3]&&delete p[3],""===p[4]&&delete p[4],""===p[5]&&delete p[5]);var v=p[3]||p[4]||p[5]||"";f[d]={name:p[1],value:Xt(v,t.shouldDecodeNewlines)}}i||(c.push({tag:n,attrs:f}),s=n,r=""),t.start&&t.start(n,f,i,e.start,e.end)}function a(e,n,r,i){var a;if(null==r&&(r=f),null==i&&(i=f),n){var o=n.toLowerCase();for(a=c.length-1;a>=0&&c[a].tag.toLowerCase()!==o;a--);}else a=0;if(a>=0){for(var u=c.length-1;u>=a;u--)t.end&&t.end(c[u].tag,r,i);c.length=a,s=a&&c[a-1].tag}else"br"===n.toLowerCase()?t.start&&t.start(n,[],!0,r,i):"p"===n.toLowerCase()&&(t.start&&t.start(n,[],!1,r,i),t.end&&t.end(n,r,i))}for(var o,s,c=[],u=t.expectHTML,l=t.isUnaryTag||Or,f=0;e;){if(o=e,s&&Ja(s)){var d=s.toLowerCase(),p=qa[d]||(qa[d]=new RegExp("([\\s\\S]*?)(</"+d+"[^>]*>)","i")),v=0,h=e.replace(p,function(e,n,r){return v=r.length,"script"!==d&&"style"!==d&&"noscript"!==d&&(n=n.replace(/<!--([\s\S]*?)-->/g,"$1").replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g,"$1")),t.chars&&t.chars(n),""});f+=e.length-h.length,e=h,a("</"+d+">",d,f-v,f)}else{var m=e.indexOf("<");if(0===m){if(/^<!--/.test(e)){var g=e.indexOf("-->");if(g>=0){n(g+3);continue}}if(/^<!\[/.test(e)){var y=e.indexOf("]>");if(y>=0){n(y+2);continue}}var _=e.match(Aa);if(_){n(_[0].length);continue}var b=e.match(ka);if(b){var $=f;n(b[0].length),a(b[0],b[1],$,f);continue}var w=r();if(w){i(w);continue}}var C=void 0;m>=0?(C=e.substring(0,m),n(m)):(C=e,e=""),t.chars&&t.chars(C)}if(e===o)throw new Error("Error parsing template:\n\n"+e)}a()}function tn(e){function t(){(o||(o=[])).push(e.slice(d,i).trim()),d=i+1}var n,r,i,a,o,s=!1,c=!1,u=0,l=0,f=0,d=0;for(i=0;i<e.length;i++)if(r=n,n=e.charCodeAt(i),s)39===n&&92!==r&&(s=!s);else if(c)34===n&&92!==r&&(c=!c);else if(124!==n||124===e.charCodeAt(i+1)||124===e.charCodeAt(i-1)||u||l||f)switch(n){case 34:c=!0;break;case 39:s=!0;break;case 40:f++;break;case 41:f--;break;case 91:l++;break;case 93:l--;break;case 123:u++;break;case 125:u--}else void 0===a?(d=i+1,a=e.slice(0,i).trim()):t();if(void 0===a?a=e.slice(0,i).trim():0!==d&&t(),o)for(i=0;i<o.length;i++)a=nn(a,o[i]);return a}function nn(e,t){var n=t.indexOf("(");if(n<0)return'_f("'+t+'")('+e+")";var r=t.slice(0,n),i=t.slice(n+1);return'_f("'+r+'")('+e+","+i}function rn(e,t){var n=t?eo(t):Qa;if(n.test(e)){for(var r,i,a=[],o=n.lastIndex=0;r=n.exec(e);){i=r.index,i>o&&a.push(JSON.stringify(e.slice(o,i)));var s=tn(r[1].trim());a.push("_s("+s+")"),o=i+r[0].length}return o<e.length&&a.push(JSON.stringify(e.slice(o))),a.join("+")}}function an(e){console.error("[Vue parser]: "+e)}function on(e,t){return e?e.map(function(e){return e[t]}).filter(function(e){return e}):[]}function sn(e,t,n){(e.props||(e.props=[])).push({name:t,value:n})}function cn(e,t,n){(e.attrs||(e.attrs=[])).push({name:t,value:n})}function un(e,t,n,r,i,a){(e.directives||(e.directives=[])).push({name:t,rawName:n,value:r,arg:i,modifiers:a})}function ln(e,t,n,r,i){r&&r.capture&&(delete r.capture,t="!"+t);var a;r&&r.native?(delete r.native,a=e.nativeEvents||(e.nativeEvents={})):a=e.events||(e.events={});var o={value:n,modifiers:r},s=a[t];Array.isArray(s)?i?s.unshift(o):s.push(o):s?a[t]=i?[o,s]:[s,o]:a[t]=o}function fn(e,t,n){var r=dn(e,":"+t)||dn(e,"v-bind:"+t);if(null!=r)return r;if(n!==!1){var i=dn(e,t);if(null!=i)return JSON.stringify(i)}}function dn(e,t){var n;if(null!=(n=e.attrsMap[t]))for(var r=e.attrsList,i=0,a=r.length;i<a;i++)if(r[i].name===t){r.splice(i,1);break}return n}function pn(e,t){Ta=t.warn||an,Sa=t.getTagNamespace||Or,Ea=t.mustUseProp||Or,ja=t.isPreTag||Or,La=on(t.modules,"preTransformNode"),Na=on(t.modules,"transformNode"),Da=on(t.modules,"postTransformNode"),Ma=t.delimiters;var n,r,i=[],a=t.preserveWhitespace!==!1,o=!1,s=!1;return en(e,{expectHTML:t.expectHTML,isUnaryTag:t.isUnaryTag,shouldDecodeNewlines:t.shouldDecodeNewlines,start:function(e,a,c){function u(e){}var l=r&&r.ns||Sa(e);t.isIE&&"svg"===l&&(a=En(a));var f={type:1,tag:e,attrsList:a,attrsMap:On(a,t.isIE),parent:r,children:[]};l&&(f.ns=l),Sn(f)&&(f.forbidden=!0);for(var d=0;d<La.length;d++)La[d](f,t);if(o||(vn(f),f.pre&&(o=!0)),ja(f.tag)&&(s=!0),o)hn(f);else{yn(f),_n(f),$n(f),mn(f),f.plain=!f.key&&!a.length,gn(f),wn(f),Cn(f);for(var p=0;p<Na.length;p++)Na[p](f,t);xn(f)}n||(n=f,u(n)),r&&!f.forbidden&&(f.else?bn(f,r):(r.children.push(f),f.parent=r)),c||(r=f,i.push(f));for(var v=0;v<Da.length;v++)Da[v](f,t)},end:function(){var e=i[i.length-1],t=e.children[e.children.length-1];t&&3===t.type&&" "===t.text&&e.children.pop(),i.length-=1,r=i[i.length-1],e.pre&&(o=!1),ja(e.tag)&&(s=!1)},chars:function(e){if(r&&(e=s||e.trim()?uo(e):a&&r.children.length?" ":"")){var t;!o&&" "!==e&&(t=rn(e,Ma))?r.children.push({type:2,expression:t,text:e}):(e=e.replace(co,""),r.children.push({type:3,text:e}))}}}),n}function vn(e){null!=dn(e,"v-pre")&&(e.pre=!0)}function hn(e){var t=e.attrsList.length;if(t)for(var n=e.attrs=new Array(t),r=0;r<t;r++)n[r]={name:e.attrsList[r].name,value:JSON.stringify(e.attrsList[r].value)};else e.pre||(e.plain=!0)}function mn(e){var t=fn(e,"key");t&&(e.key=t)}function gn(e){var t=fn(e,"ref");t&&(e.ref=t,e.refInFor=kn(e))}function yn(e){var t;if(t=dn(e,"v-for")){var n=t.match(no);if(!n)return;e.for=n[2].trim();var r=n[1].trim(),i=r.match(ro);i?(e.alias=i[1].trim(),e.iterator1=i[2].trim(),i[3]&&(e.iterator2=i[3].trim())):e.alias=r}}function _n(e){var t=dn(e,"v-if");t&&(e.if=t),null!=dn(e,"v-else")&&(e.else=!0)}function bn(e,t){var n=Tn(t.children);n&&n.if&&(n.elseBlock=e)}function $n(e){var t=dn(e,"v-once");null!=t&&(e.once=!0)}function wn(e){if("slot"===e.tag)e.slotName=fn(e,"name");else{var t=fn(e,"slot");t&&(e.slotTarget=t)}}function Cn(e){var t;(t=fn(e,"is"))&&(e.component=t),null!=dn(e,"inline-template")&&(e.inlineTemplate=!0)}function xn(e){var t,n,r,i,a,o,s,c,u=e.attrsList;for(t=0,n=u.length;t<n;t++)if(r=i=u[t].name,a=u[t].value,to.test(r))if(e.hasBindings=!0,s=An(r),s&&(r=r.replace(so,"")),io.test(r))r=r.replace(io,""),s&&s.prop&&(c=!0,r=$r(r),"innerHtml"===r&&(r="innerHTML")),c||Ea(r)?sn(e,r,a):cn(e,r,a);else if(ao.test(r))r=r.replace(ao,""),ln(e,r,a,s);else{r=r.replace(to,"");var l=r.match(oo);l&&(o=l[1])&&(r=r.slice(0,-(o.length+1))),un(e,r,i,a,o,s)}else cn(e,r,JSON.stringify(a))}function kn(e){for(var t=e;t;){if(void 0!==t.for)return!0;t=t.parent}return!1}function An(e){var t=e.match(so);if(t){var n={};return t.forEach(function(e){n[e.slice(1)]=!0}),n}}function On(e,t){for(var n={},r=0,i=e.length;r<i;r++)n[e[r].name]=e[r].value;return n}function Tn(e){for(var t=e.length;t--;)if(e[t].tag)return e[t]}function Sn(e){return"style"===e.tag||"script"===e.tag&&(!e.attrsMap.type||"text/javascript"===e.attrsMap.type)}function En(e){for(var t=[],n=0;n<e.length;n++){var r=e[n];lo.test(r.name)||(r.name=r.name.replace(fo,""),t.push(r))}return t}function jn(e,t){e&&(Pa=po(t.staticKeys||""),Ra=t.isReservedTag||function(){return!1},Nn(e),Dn(e,!1))}function Ln(e){return n("type,tag,attrsList,attrsMap,plain,parent,children,attrs"+(e?","+e:""))}function Nn(e){if(e.static=Mn(e),1===e.type)for(var t=0,n=e.children.length;t<n;t++){var r=e.children[t];Nn(r),r.static||(e.static=!1)}}function Dn(e,t){if(1===e.type){if(e.once||e.static)return e.staticRoot=!0,void(e.staticInFor=t);if(e.children)for(var n=0,r=e.children.length;n<r;n++)Dn(e.children[n],t||!!e.for)}}function Mn(e){return 2!==e.type&&(3===e.type||!(!e.pre&&(e.hasBindings||e.if||e.for||yr(e.tag)||!Ra(e.tag)||Pn(e)||!Object.keys(e).every(Pa))))}function Pn(e){for(;e.parent;){if(e=e.parent,"template"!==e.tag)return!1;if(e.for)return!0}return!1}function Rn(e,t){var n=t?"nativeOn:{":"on:{";for(var r in e)n+='"'+r+'":'+In(e[r])+",";return n.slice(0,-1)+"}"}function In(e){if(e){if(Array.isArray(e))return"["+e.map(In).join(",")+"]";if(e.modifiers){var t="",n=[];for(var r in e.modifiers)mo[r]?t+=mo[r]:n.push(r);n.length&&(t=Bn(n)+t);var i=vo.test(e.value)?e.value+"($event)":e.value;return"function($event){"+t+i+"}"}return vo.test(e.value)?e.value:"function($event){"+e.value+"}"}return"function(){}"}function Bn(e){var t=1===e.length?Fn(e[0]):Array.prototype.concat.apply([],e.map(Fn));return Array.isArray(t)?"if("+t.map(function(e){return"$event.keyCode!=="+e}).join("&&")+")return;":"if($event.keyCode!=="+t+")return;"}function Fn(e){return parseInt(e,10)||ho[e]||"_k("+JSON.stringify(e)+")"}function Hn(e,t){e.wrapData=function(e){return"_b("+e+","+t.value+(t.modifiers&&t.modifiers.prop?",true":"")+")"}}function Un(e,t){var n=Ua,r=Ua=[];za=t,Ia=t.warn||an,Ba=on(t.modules,"transformCode"),Fa=on(t.modules,"genData"),Ha=t.directives||{};var i=e?zn(e):'_h("div")';return Ua=n,{render:"with(this){return "+i+"}",staticRenderFns:r}}function zn(e){if(e.staticRoot&&!e.staticProcessed)return e.staticProcessed=!0,Ua.push("with(this){return "+zn(e)+"}"),"_m("+(Ua.length-1)+(e.staticInFor?",true":"")+")";if(e.for&&!e.forProcessed)return qn(e);if(e.if&&!e.ifProcessed)return Vn(e);if("template"!==e.tag||e.slotTarget){if("slot"===e.tag)return Qn(e);var t;if(e.component)t=Xn(e);else{var n=Kn(e),r=e.inlineTemplate?null:Zn(e);t="_h('"+e.tag+"'"+(n?","+n:"")+(r?","+r:"")+")"}for(var i=0;i<Ba.length;i++)t=Ba[i](e,t);return t}return Zn(e)||"void 0"}function Vn(e){var t=e.if;return e.ifProcessed=!0,"("+t+")?"+zn(e)+":"+Jn(e)}function Jn(e){return e.elseBlock?zn(e.elseBlock):"_e()"}function qn(e){var t=e.for,n=e.alias,r=e.iterator1?","+e.iterator1:"",i=e.iterator2?","+e.iterator2:"";return e.forProcessed=!0,"_l(("+t+"),function("+n+r+i+"){return "+zn(e)+"})"}function Kn(e){if(!e.plain){var t="{",n=Wn(e);n&&(t+=n+","),e.key&&(t+="key:"+e.key+","),e.ref&&(t+="ref:"+e.ref+","),e.refInFor&&(t+="refInFor:true,"),e.component&&(t+='tag:"'+e.tag+'",'),e.slotTarget&&(t+="slot:"+e.slotTarget+",");for(var r=0;r<Fa.length;r++)t+=Fa[r](e);if(e.attrs&&(t+="attrs:{"+er(e.attrs)+"},"),e.props&&(t+="domProps:{"+er(e.props)+"},"),e.events&&(t+=Rn(e.events)+","),e.nativeEvents&&(t+=Rn(e.nativeEvents,!0)+","),e.inlineTemplate){var i=e.children[0];if(1===i.type){var a=Un(i,za);t+="inlineTemplate:{render:function(){"+a.render+"},staticRenderFns:["+a.staticRenderFns.map(function(e){return"function(){"+e+"}"}).join(",")+"]}"}}return t=t.replace(/,$/,"")+"}",e.wrapData&&(t=e.wrapData(t)),t}}function Wn(e){var t=e.directives;if(t){var n,r,i,a,o="directives:[",s=!1;for(n=0,r=t.length;n<r;n++){i=t[n],a=!0;var c=Ha[i.name]||go[i.name];c&&(a=!!c(e,i,Ia)),a&&(s=!0,o+='{name:"'+i.name+'",rawName:"'+i.rawName+'"'+(i.value?",value:("+i.value+"),expression:"+JSON.stringify(i.value):"")+(i.arg?',arg:"'+i.arg+'"':"")+(i.modifiers?",modifiers:"+JSON.stringify(i.modifiers):"")+"},")}return s?o.slice(0,-1)+"]":void 0}}function Zn(e){if(e.children.length)return"["+e.children.map(Gn).join(",")+"]"}function Gn(e){return 1===e.type?zn(e):Yn(e)}function Yn(e){return 2===e.type?e.expression:JSON.stringify(e.text)}function Qn(e){var t=e.slotName||'"default"',n=Zn(e);return n?"_t("+t+","+n+")":"_t("+t+")"}function Xn(e){var t=e.inlineTemplate?null:Zn(e);return"_h("+e.component+","+Kn(e)+(t?","+t:"")+")"}function er(e){for(var t="",n=0;n<e.length;n++){var r=e[n];t+='"'+r.name+'":'+r.value+","}return t.slice(0,-1)}function tr(e,t){var n=pn(e.trim(),t);jn(n,t);var r=Un(n,t);return{ast:n,render:r.render,staticRenderFns:r.staticRenderFns}}function nr(e,t){var n=(t.warn||an,dn(e,"class"));n&&(e.staticClass=JSON.stringify(n));var r=fn(e,"class",!1);r&&(e.classBinding=r)}function rr(e){var t="";return e.staticClass&&(t+="staticClass:"+e.staticClass+","),e.classBinding&&(t+="class:"+e.classBinding+","),t}function ir(e){var t=fn(e,"style",!1);t&&(e.styleBinding=t)}function ar(e){return e.styleBinding?"style:("+e.styleBinding+"),":""}function or(e,t,n){Va=n;var r=t.value,i=t.modifiers,a=e.tag,o=e.attrsMap.type;return"select"===a?lr(e,r):"input"===a&&"checkbox"===o?sr(e,r):"input"===a&&"radio"===o?cr(e,r):ur(e,r,i),!0}function sr(e,t){var n=fn(e,"value")||"null",r=fn(e,"true-value")||"true",i=fn(e,"false-value")||"false";sn(e,"checked","Array.isArray("+t+")?_i("+t+","+n+")>-1:_q("+t+","+r+")"),ln(e,"change","var $$a="+t+",$$el=$event.target,$$c=$$el.checked?("+r+"):("+i+");if(Array.isArray($$a)){var $$v="+n+",$$i=_i($$a,$$v);if($$c){$$i<0&&("+t+"=$$a.concat($$v))}else{$$i>-1&&("+t+"=$$a.slice(0,$$i).concat($$a.slice($$i+1)))}}else{"+t+"=$$c}",null,!0)}function cr(e,t){var n=fn(e,"value")||"null";sn(e,"checked","_q("+t+","+n+")"),ln(e,"change",t+"="+n,null,!0)}function ur(e,t,n){var r=e.attrsMap.type,i=n||{},a=i.lazy,o=i.number,s=i.trim,c=a||Nr&&"range"===r?"change":"input",u=!a&&"range"!==r,l="input"===e.tag||"textarea"===e.tag,f=l?"$event.target.value"+(s?".trim()":""):"$event",d=o||"number"===r?t+"=_n("+f+")":t+"="+f;l&&u&&(d="if($event.target.composing)return;"+d),sn(e,"value",l?"_s("+t+")":"("+t+")"),ln(e,c,d,null,!0)}function lr(e,t){var n=t+'=Array.prototype.filter.call($event.target.options,function(o){return o.selected}).map(function(o){return "_value" in o ? o._value : o.value})'+(null==e.attrsMap.multiple?"[0]":"");ln(e,"change",n,null,!0)}function fr(e,t){t.value&&sn(e,"textContent","_s("+t.value+")")}function dr(e,t){t.value&&sn(e,"innerHTML","_s("+t.value+")")}function pr(e,t){return t=t?u(u({},Co),t):Co,tr(e,t)}function vr(e,t,n){var r=(t&&t.warn||li,t&&t.delimiters?String(t.delimiters)+e:e);if(wo[r])return wo[r];var i={},a=pr(e,t);i.render=hr(a.render);var o=a.staticRenderFns.length;i.staticRenderFns=new Array(o);for(var s=0;s<o;s++)i.staticRenderFns[s]=hr(a.staticRenderFns[s]);return wo[r]=i}function hr(e){try{return new Function(e)}catch(e){return p}}function mr(e){if(e.outerHTML)return e.outerHTML;var t=document.createElement("div");return t.appendChild(e.cloneNode(!0)),t.innerHTML}var gr,yr=n("slot,component",!0),_r=Object.prototype.hasOwnProperty,br=/-(\w)/g,$r=o(function(e){return e.replace(br,function(e,t){return t?t.toUpperCase():""})}),wr=o(function(e){return e.charAt(0).toUpperCase()+e.slice(1)}),Cr=/([^-])([A-Z])/g,xr=o(function(e){return e.replace(Cr,"$1-$2").replace(Cr,"$1-$2").toLowerCase()}),kr=Object.prototype.toString,Ar="[object Object]",Or=function(){return!1},Tr={optionMergeStrategies:Object.create(null),silent:!1,devtools:!1,errorHandler:null,ignoredElements:null,keyCodes:Object.create(null),isReservedTag:Or,isUnknownElement:Or,getTagNamespace:p,mustUseProp:Or,_assetTypes:["component","directive","filter"],_lifecycleHooks:["beforeCreate","created","beforeMount","mounted","beforeUpdate","updated","beforeDestroy","destroyed","activated","deactivated"],_maxUpdateCount:100,_isServer:!1},Sr=/[^\w\.\$]/,Er="__proto__"in{},jr="undefined"!=typeof window&&"[object Object]"!==Object.prototype.toString.call(window),Lr=jr&&window.navigator.userAgent.toLowerCase(),Nr=Lr&&/msie|trident/.test(Lr),Dr=Lr&&Lr.indexOf("msie 9.0")>0,Mr=Lr&&Lr.indexOf("edge/")>0,Pr=Lr&&Lr.indexOf("android")>0,Rr=Lr&&/iphone|ipad|ipod|ios/.test(Lr),Ir=jr&&window.__VUE_DEVTOOLS_GLOBAL_HOOK__,Br=function(){function e(){r=!1;var e=n.slice(0);n.length=0;for(var t=0;t<e.length;t++)e[t]()}var t,n=[],r=!1;if("undefined"!=typeof Promise&&b(Promise)){var i=Promise.resolve();t=function(){i.then(e),Rr&&setTimeout(p)}}else if("undefined"==typeof MutationObserver||!b(MutationObserver)&&"[object MutationObserverConstructor]"!==MutationObserver.toString())t=function(){setTimeout(e,0)};else{var a=1,o=new MutationObserver(e),s=document.createTextNode(String(a));o.observe(s,{characterData:!0}),t=function(){a=(a+1)%2,s.data=String(a)}}return function(e,i){var a=i?function(){e.call(i)}:e;n.push(a),r||(r=!0,t())}}();gr="undefined"!=typeof Set&&b(Set)?Set:function(){function e(){this.set=Object.create(null)}return e.prototype.has=function(e){return void 0!==this.set[e]},e.prototype.add=function(e){this.set[e]=1},e.prototype.clear=function(){this.set=Object.create(null)},e}();var Fr=0,Hr=function(){this.id=Fr++,this.subs=[]};Hr.prototype.addSub=function(e){this.subs.push(e)},Hr.prototype.removeSub=function(e){r(this.subs,e)},Hr.prototype.depend=function(){Hr.target&&Hr.target.addDep(this)},Hr.prototype.notify=function(){for(var e=this.subs.slice(),t=0,n=e.length;t<n;t++)e[t].update()},Hr.target=null;var Ur=[],zr=[],Vr={},Jr=!1,qr=!1,Kr=0,Wr=0,Zr=function(e,t,n,r){void 0===r&&(r={}),this.vm=e,e._watchers.push(this),this.deep=!!r.deep,this.user=!!r.user,this.lazy=!!r.lazy,this.sync=!!r.sync,this.expression=t.toString(),this.cb=n,this.id=++Wr,this.active=!0,this.dirty=this.lazy,this.deps=[],this.newDeps=[],this.depIds=new gr,this.newDepIds=new gr,"function"==typeof t?this.getter=t:(this.getter=_(t),this.getter||(this.getter=function(){})),this.value=this.lazy?void 0:this.get()};Zr.prototype.get=function(){$(this);var e=this.getter.call(this.vm,this.vm);return this.deep&&A(e),w(),this.cleanupDeps(),e},Zr.prototype.addDep=function(e){var t=e.id;this.newDepIds.has(t)||(this.newDepIds.add(t),this.newDeps.push(e),this.depIds.has(t)||e.addSub(this))},Zr.prototype.cleanupDeps=function(){for(var e=this,t=this.deps.length;t--;){var n=e.deps[t];e.newDepIds.has(n.id)||n.removeSub(e)}var r=this.depIds;this.depIds=this.newDepIds,this.newDepIds=r,this.newDepIds.clear(),r=this.deps,this.deps=this.newDeps,this.newDeps=r,this.newDeps.length=0},Zr.prototype.update=function(){this.lazy?this.dirty=!0:this.sync?this.run():k(this)},Zr.prototype.run=function(){if(this.active){var e=this.get();if(e!==this.value||l(e)||this.deep){var t=this.value;if(this.value=e,this.user)try{this.cb.call(this.vm,e,t)}catch(e){if(!Tr.errorHandler)throw e;Tr.errorHandler.call(null,e,this.vm)}else this.cb.call(this.vm,e,t)}}},Zr.prototype.evaluate=function(){this.value=this.get(),this.dirty=!1},Zr.prototype.depend=function(){for(var e=this,t=this.deps.length;t--;)e.deps[t].depend()},Zr.prototype.teardown=function(){var e=this;if(this.active){this.vm._isBeingDestroyed||this.vm._vForRemoving||r(this.vm._watchers,this);for(var t=this.deps.length;t--;)e.deps[t].removeSub(e);this.active=!1}};var Gr=new gr,Yr=Array.prototype,Qr=Object.create(Yr);["push","pop","shift","unshift","splice","sort","reverse"].forEach(function(e){var t=Yr[e];y(Qr,e,function(){for(var n=arguments,r=arguments.length,i=new Array(r);r--;)i[r]=n[r];var a,o=t.apply(this,i),s=this.__ob__;switch(e){case"push":a=i;break;case"unshift":a=i;break;case"splice":a=i.slice(2)}return a&&s.observeArray(a),s.dep.notify(),o})});var Xr=Object.getOwnPropertyNames(Qr),ei={shouldConvert:!0,isSettingProps:!1},ti=function(e){if(this.value=e,this.dep=new Hr,this.vmCount=0,y(e,"__ob__",this),Array.isArray(e)){var t=Er?O:T;t(e,Qr,Xr),this.observeArray(e)}else this.walk(e)};ti.prototype.walk=function(e){for(var t=Object.keys(e),n=0;n<t.length;n++)E(e,t[n],e[t[n]])},ti.prototype.observeArray=function(e){for(var t=0,n=e.length;t<n;t++)S(e[t])};var ni={enumerable:!0,configurable:!0,get:p,set:p},ri=function(e,t,n,r,i,a,o,s){this.tag=e,this.data=t,this.children=n,this.text=r,this.elm=i,this.ns=a,this.context=o,this.functionalContext=void 0,this.key=t&&t.key,this.componentOptions=s,this.child=void 0,this.parent=void 0,this.raw=!1,this.isStatic=!1,this.isRootInsert=!0,this.isComment=!1,this.isCloned=!1},ii=function(){var e=new ri;return e.text="",e.isComment=!0,e},ai=null,oi={init:oe,prepatch:se,insert:ce,destroy:ue},si=Object.keys(oi),ci=0;we(Ce),U(Ce),$e(Ce),te(Ce),ye(Ce);var ui,li=p,fi=Tr.optionMergeStrategies;fi.data=function(e,t,n){return n?e||t?function(){var r="function"==typeof t?t.call(n):t,i="function"==typeof e?e.call(n):void 0;return r?xe(r,i):i}:void 0:t?"function"!=typeof t?e:e?function(){return xe(t.call(this),e.call(this))}:t:e},Tr._lifecycleHooks.forEach(function(e){fi[e]=ke}),Tr._assetTypes.forEach(function(e){fi[e+"s"]=Ae}),fi.watch=function(e,t){if(!t)return e;if(!e)return t;var n={};u(n,e);for(var r in t){var i=n[r],a=t[r];i&&!Array.isArray(i)&&(i=[i]),n[r]=i?i.concat(a):[a]}return n},fi.props=fi.methods=fi.computed=function(e,t){if(!t)return e;if(!e)return t;var n=Object.create(null);return u(n,e),u(n,t),n};var di=function(e,t){return void 0===t?e:t},pi=Object.freeze({defineReactive:E,_toString:e,toNumber:t,makeMap:n,isBuiltInTag:yr,remove:r,hasOwn:i,isPrimitive:a,cached:o,camelize:$r,capitalize:wr,hyphenate:xr,bind:s,toArray:c,extend:u,isObject:l,isPlainObject:f,toObject:d,noop:p,no:Or,genStaticKeys:v,looseEqual:h,looseIndexOf:m,isReserved:g,def:y,parsePath:_,hasProto:Er,inBrowser:jr,UA:Lr,isIE:Nr,isIE9:Dr,isEdge:Mr,isAndroid:Pr,isIOS:Rr,devtools:Ir,nextTick:Br,get _Set(){return gr},mergeOptions:Ee,resolveAsset:je,warn:li,formatComponentName:ui,validateProp:Le}),vi={name:"keep-alive",abstract:!0,created:function(){this.cache=Object.create(null)},render:function(){var e=X(this.$slots.default);if(e&&e.componentOptions){var t=e.componentOptions,n=null==e.key?t.Ctor.cid+"::"+t.tag:e.key;this.cache[n]?e.child=this.cache[n].child:this.cache[n]=e,e.data.keepAlive=!0}return e},destroyed:function(){var e=this;for(var t in this.cache){var n=e.cache[t];ne(n.child,"deactivated"),n.child.$destroy()}}},hi={KeepAlive:vi};Fe(Ce),Object.defineProperty(Ce.prototype,"$isServer",{get:function(){return Tr._isServer}}),Ce.version="2.0.3";var mi,gi=n("value,selected,checked,muted"),yi=n("contenteditable,draggable,spellcheck"),_i=n("allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,defaultchecked,defaultmuted,defaultselected,defer,disabled,enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,translate,truespeed,typemustmatch,visible"),bi="http://www.w3.org/1999/xlink",$i=function(e){return":"===e.charAt(5)&&"xlink"===e.slice(0,5)},wi=function(e){return $i(e)?e.slice(6,e.length):""},Ci=function(e){return null==e||e===!1},xi={svg:"http://www.w3.org/2000/svg",math:"http://www.w3.org/1998/Math/MathML"},ki=n("html,body,base,head,link,meta,style,title,address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,div,dd,dl,dt,figcaption,figure,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,s,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,output,progress,select,textarea,details,dialog,menu,menuitem,summary,content,element,shadow,template"),Ai=n("area,base,br,col,embed,frame,hr,img,input,isindex,keygen,link,meta,param,source,track,wbr",!0),Oi=n("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source",!0),Ti=n("address,article,aside,base,blockquote,body,caption,col,colgroup,dd,details,dialog,div,dl,dt,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,legend,li,menuitem,meta,optgroup,option,param,rp,rt,source,style,summary,tbody,td,tfoot,th,thead,title,tr,track",!0),Si=n("svg,animate,circle,clippath,cursor,defs,desc,ellipse,filter,font,font-face,g,glyph,image,line,marker,mask,missing-glyph,path,pattern,polygon,polyline,rect,switch,symbol,text,textpath,tspan,use,view",!0),Ei=function(e){return"pre"===e},ji=function(e){return ki(e)||Si(e)},Li=Object.create(null),Ni=Object.freeze({createElement:Ze,createElementNS:Ge,createTextNode:Ye,createComment:Qe,insertBefore:Xe,removeChild:et,appendChild:tt,parentNode:nt,nextSibling:rt,tagName:it,setTextContent:at,childNodes:ot,setAttribute:st}),Di={create:function(e,t){ct(t)},update:function(e,t){e.data.ref!==t.data.ref&&(ct(e,!0),ct(t))},destroy:function(e){ct(e,!0)}},Mi=new ri("",{},[]),Pi=["create","update","remove","destroy"],Ri={create:vt,update:vt,destroy:function(e){vt(e,Mi)}},Ii=Object.create(null),Bi=[Di,Ri],Fi={create:yt,update:yt},Hi={create:bt,update:bt},Ui={create:$t,update:$t},zi={create:wt,update:wt},Vi=["Webkit","Moz","ms"],Ji=o(function(e){if(mi=mi||document.createElement("div"),e=$r(e),"filter"!==e&&e in mi.style)return e;for(var t=e.charAt(0).toUpperCase()+e.slice(1),n=0;n<Vi.length;n++){var r=Vi[n]+t;if(r in mi.style)return r}}),qi={create:Ct,update:Ct},Ki=jr&&!Dr,Wi="transition",Zi="animation",Gi="transition",Yi="transitionend",Qi="animation",Xi="animationend";Ki&&(void 0===window.ontransitionend&&void 0!==window.onwebkittransitionend&&(Gi="WebkitTransition",Yi="webkitTransitionEnd"),void 0===window.onanimationend&&void 0!==window.onwebkitanimationend&&(Qi="WebkitAnimation",Xi="webkitAnimationEnd"));var ea=jr&&window.requestAnimationFrame||setTimeout,ta=/\b(transform|all)(,|$)/,na=o(function(e){return{enterClass:e+"-enter",leaveClass:e+"-leave",appearClass:e+"-enter",enterActiveClass:e+"-enter-active",leaveActiveClass:e+"-leave-active",appearActiveClass:e+"-enter-active"}}),ra=jr?{create:function(e,t){t.data.show||Nt(t)},remove:function(e,t){e.data.show?t():Dt(e,t)}}:{},ia=[Fi,Hi,Ui,zi,qi,ra],aa=ia.concat(Bi),oa=pt({nodeOps:Ni,modules:aa});Dr&&document.addEventListener("selectionchange",function(){var e=document.activeElement;e&&e.vmodel&&Ut(e,"input")});var sa={inserted:function(e,t,n){if("select"===n.tag){var r=function(){Rt(e,t,n.context)};r(),(Nr||Mr)&&setTimeout(r,0)}else"textarea"!==n.tag&&"text"!==e.type||t.modifiers.lazy||(Pr||(e.addEventListener("compositionstart",Ft),e.addEventListener("compositionend",Ht)),Dr&&(e.vmodel=!0))},componentUpdated:function(e,t,n){if("select"===n.tag){Rt(e,t,n.context);var r=e.multiple?t.value.some(function(t){return It(t,e.options)}):t.value!==t.oldValue&&It(t.value,e.options);r&&Ut(e,"change")}}},ca={bind:function(e,t,n){var r=t.value;n=zt(n);var i=n.data&&n.data.transition;r&&i&&!Dr&&Nt(n);var a="none"===e.style.display?"":e.style.display;e.style.display=r?a:"none",e.__vOriginalDisplay=a},update:function(e,t,n){var r=t.value,i=t.oldValue;if(r!==i){n=zt(n);var a=n.data&&n.data.transition;a&&!Dr?r?(Nt(n),e.style.display=e.__vOriginalDisplay):Dt(n,function(){e.style.display="none"}):e.style.display=r?e.__vOriginalDisplay:"none"}}},ua={model:sa,show:ca},la={name:String,appear:Boolean,css:Boolean,mode:String,type:String,enterClass:String,leaveClass:String,enterActiveClass:String,leaveActiveClass:String,appearClass:String,appearActiveClass:String},fa={name:"transition",props:la,abstract:!0,render:function(e){var t=this,n=this.$slots.default;if(n&&(n=n.filter(function(e){return e.tag}),n.length)){var r=this.mode,i=n[0];if(Kt(this.$vnode))return i;var a=Vt(i);if(!a)return i;if(this._leaving)return qt(e,i);var o=a.key=null==a.key||a.isStatic?"__v"+(a.tag+this._uid)+"__":a.key,s=(a.data||(a.data={})).transition=Jt(this),c=this._vnode,l=Vt(c);if(a.data.directives&&a.data.directives.some(function(e){return"show"===e.name})&&(a.data.show=!0),l&&l.data&&l.key!==o){var f=l.data.transition=u({},s);if("out-in"===r)return this._leaving=!0,q(f,"afterLeave",function(){t._leaving=!1,t.$forceUpdate()},o),qt(e,i);if("in-out"===r){var d,p=function(){d()};q(s,"afterEnter",p,o),q(s,"enterCancelled",p,o),q(f,"delayLeave",function(e){d=e},o)}}return i}}},da=u({tag:String,moveClass:String},la);delete da.mode;var pa={props:da,render:function(e){for(var t=this.tag||this.$vnode.data.tag||"span",n=Object.create(null),r=this.prevChildren=this.children,i=this.$slots.default||[],a=this.children=[],o=Jt(this),s=0;s<i.length;s++){var c=i[s];c.tag&&null!=c.key&&0!==String(c.key).indexOf("__vlist")&&(a.push(c),n[c.key]=c,(c.data||(c.data={})).transition=o)}if(r){for(var u=[],l=[],f=0;f<r.length;f++){var d=r[f];d.data.transition=o,d.data.pos=d.elm.getBoundingClientRect(),n[d.key]?u.push(d):l.push(d)}this.kept=e(t,null,u),this.removed=l}return e(t,null,a)},beforeUpdate:function(){this.__patch__(this._vnode,this.kept,!1,!0),this._vnode=this.kept},updated:function(){var e=this.prevChildren,t=this.moveClass||this.name+"-move";if(e.length&&this.hasMove(e[0].elm,t)){e.forEach(Wt),e.forEach(Zt),e.forEach(Gt);document.body.offsetHeight;e.forEach(function(e){if(e.data.moved){var n=e.elm,r=n.style;Ot(n,t),r.transform=r.WebkitTransform=r.transitionDuration="",n.addEventListener(Yi,n._moveCb=function e(r){r&&!/transform$/.test(r.propertyName)||(n.removeEventListener(Yi,e),n._moveCb=null,Tt(n,t))})}})}},methods:{hasMove:function(e,t){if(!Ki)return!1;if(null!=this._hasMove)return this._hasMove;Ot(e,t);var n=Et(e);return Tt(e,t),this._hasMove=n.hasTransform}}},va={Transition:fa,TransitionGroup:pa};Ce.config.isUnknownElement=Ke,Ce.config.isReservedTag=ji,Ce.config.getTagNamespace=qe,Ce.config.mustUseProp=gi,u(Ce.options.directives,ua),u(Ce.options.components,va),Ce.prototype.__patch__=Tr._isServer?p:oa,Ce.prototype.$mount=function(e,t){return e=e&&!Tr._isServer?We(e):void 0,this._mount(e,t)},setTimeout(function(){Tr.devtools&&Ir&&Ir.emit("init",Ce)},0);var ha=!!jr&&Yt("\n","&#10;"),ma=document.createElement("div"),ga=/([^\s"'<>\/=]+)/,ya=/(?:=)/,_a=[/"([^"]*)"+/.source,/'([^']*)'+/.source,/([^\s"'=<>`]+)/.source],ba=new RegExp("^\\s*"+ga.source+"(?:\\s*("+ya.source+")\\s*(?:"+_a.join("|")+"))?"),$a="[a-zA-Z_][\\w\\-\\.]*",wa="((?:"+$a+"\\:)?"+$a+")",Ca=new RegExp("^<"+wa),xa=/^\s*(\/?)>/,ka=new RegExp("^<\\/"+wa+"[^>]*>"),Aa=/^<!DOCTYPE [^>]+>/i,Oa=!1;"x".replace(/x(.)?/g,function(e,t){Oa=""===t});var Ta,Sa,Ea,ja,La,Na,Da,Ma,Pa,Ra,Ia,Ba,Fa,Ha,Ua,za,Va,Ja=n("script,style",!0),qa={},Ka=/&lt;/g,Wa=/&gt;/g,Za=/&#10;/g,Ga=/&amp;/g,Ya=/&quot;/g,Qa=/\{\{((?:.|\n)+?)\}\}/g,Xa=/[-.*+?^${}()|[\]\/\\]/g,eo=o(function(e){var t=e[0].replace(Xa,"\\$&"),n=e[1].replace(Xa,"\\$&");return new RegExp(t+"((?:.|\\n)+?)"+n,"g")}),to=/^v-|^@|^:/,no=/(.*?)\s+(?:in|of)\s+(.*)/,ro=/\(([^,]*),([^,]*)(?:,([^,]*))?\)/,io=/^:|^v-bind:/,ao=/^@|^v-on:/,oo=/:(.*)$/,so=/\.[^\.]+/g,co=/\u2028|\u2029/g,uo=o(Qt),lo=/^xmlns:NS\d+/,fo=/^NS\d+:/,po=o(Ln),vo=/^\s*[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['.*?'\]|\[".*?"\]|\[\d+\]|\[[A-Za-z_$][\w$]*\])*\s*$/,ho={esc:27,tab:9,enter:13,space:32,up:38,left:37,right:39,down:40,delete:[8,46]},mo={stop:"$event.stopPropagation();",prevent:"$event.preventDefault();",self:"if($event.target !== $event.currentTarget)return;"},go={bind:Hn,cloak:p},yo=(new RegExp("\\b"+"do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,super,throw,while,yield,delete,export,import,return,switch,default,extends,finally,continue,debugger,function,arguments".split(",").join("\\b|\\b")+"\\b"),{staticKeys:["staticClass"],transformNode:nr,genData:rr}),_o={transformNode:ir,genData:ar},bo=[yo,_o],$o={model:or,text:fr,html:dr},wo=Object.create(null),Co={isIE:Nr,expectHTML:!0,modules:bo,staticKeys:v(bo),directives:$o,isReservedTag:ji,isUnaryTag:Ai,mustUseProp:gi,getTagNamespace:qe,isPreTag:Ei},xo=o(function(e){var t=We(e);return t&&t.innerHTML}),ko=Ce.prototype.$mount;return Ce.prototype.$mount=function(e,t){if(e=e&&We(e),e===document.body||e===document.documentElement)return this;var n=this.$options;if(!n.render){var r=n.template;if(r)if("string"==typeof r)"#"===r.charAt(0)&&(r=xo(r));else{if(!r.nodeType)return this;r=r.innerHTML}else e&&(r=mr(e));if(r){var i=vr(r,{warn:li,shouldDecodeNewlines:ha,delimiters:n.delimiters},this),a=i.render,o=i.staticRenderFns;n.render=a,n.staticRenderFns=o}}return ko.call(this,e,t)},Ce.compile=vr,Ce}); \ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
new file mode 100644
index 00000000000..1eaa04e21c1
--- /dev/null
+++ b/yarn.lock
@@ -0,0 +1,4632 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+abbrev@1, abbrev@1.0.x:
+ version "1.0.9"
+ resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135"
+
+accepts@1.3.3, accepts@~1.3.3:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca"
+ dependencies:
+ mime-types "~2.1.11"
+ negotiator "0.6.1"
+
+acorn-dynamic-import@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-2.0.1.tgz#23f671eb6e650dab277fef477c321b1178a8cca2"
+ dependencies:
+ acorn "^4.0.3"
+
+acorn-jsx@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b"
+ dependencies:
+ acorn "^3.0.4"
+
+acorn@4.0.4, acorn@^4.0.3, acorn@^4.0.4:
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.4.tgz#17a8d6a7a6c4ef538b814ec9abac2779293bf30a"
+
+acorn@^3.0.4:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a"
+
+acorn@^4.0.11:
+ version "4.0.11"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.11.tgz#edcda3bd937e7556410d42ed5860f67399c794c0"
+
+after@0.8.2:
+ version "0.8.2"
+ resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"
+
+ajv-keywords@^1.0.0, ajv-keywords@^1.1.1:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c"
+
+ajv@^4.7.0:
+ version "4.11.2"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.2.tgz#f166c3c11cbc6cb9dcc102a5bcfe5b72c95287e6"
+ dependencies:
+ co "^4.6.0"
+ json-stable-stringify "^1.0.1"
+
+align-text@^0.1.1, align-text@^0.1.3:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117"
+ dependencies:
+ kind-of "^3.0.2"
+ longest "^1.0.1"
+ repeat-string "^1.5.2"
+
+amdefine@>=0.0.4:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
+
+ansi-escapes@^1.1.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e"
+
+ansi-html@0.0.7:
+ version "0.0.7"
+ resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e"
+
+ansi-regex@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
+
+ansi-styles@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
+
+anymatch@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.0.tgz#a3e52fa39168c825ff57b0248126ce5a8ff95507"
+ dependencies:
+ arrify "^1.0.0"
+ micromatch "^2.1.5"
+
+append-transform@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-0.4.0.tgz#d76ebf8ca94d276e247a36bad44a4b74ab611991"
+ dependencies:
+ default-require-extensions "^1.0.0"
+
+aproba@^1.0.3:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.1.0.tgz#4d8f047a318604e18e3c06a0e52230d3d19f147b"
+
+are-we-there-yet@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz#80e470e95a084794fe1899262c5667c6e88de1b3"
+ dependencies:
+ delegates "^1.0.0"
+ readable-stream "^2.0.0 || ^1.1.13"
+
+argparse@^1.0.7:
+ version "1.0.9"
+ resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86"
+ dependencies:
+ sprintf-js "~1.0.2"
+
+arr-diff@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf"
+ dependencies:
+ arr-flatten "^1.0.1"
+
+arr-flatten@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.0.1.tgz#e5ffe54d45e19f32f216e91eb99c8ce892bb604b"
+
+array-find@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/array-find/-/array-find-1.0.0.tgz#6c8e286d11ed768327f8e62ecee87353ca3e78b8"
+
+array-flatten@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
+
+array-slice@^0.2.3:
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-0.2.3.tgz#dd3cfb80ed7973a75117cdac69b0b99ec86186f5"
+
+array-union@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39"
+ dependencies:
+ array-uniq "^1.0.1"
+
+array-uniq@^1.0.1:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
+
+array-unique@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53"
+
+arraybuffer.slice@0.0.6:
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz#f33b2159f0532a3f3107a272c0ccfbd1ad2979ca"
+
+arrify@^1.0.0, arrify@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
+
+asn1.js@^4.0.0:
+ version "4.9.1"
+ resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.9.1.tgz#48ba240b45a9280e94748990ba597d216617fd40"
+ dependencies:
+ bn.js "^4.0.0"
+ inherits "^2.0.1"
+ minimalistic-assert "^1.0.0"
+
+asn1@~0.2.3:
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86"
+
+assert-plus@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234"
+
+assert-plus@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
+
+assert@^1.1.1:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91"
+ dependencies:
+ util "0.10.3"
+
+async-each@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
+
+async@0.2.x, async@~0.2.6:
+ version "0.2.10"
+ resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
+
+async@1.x, async@^1.4.0, async@^1.4.2, async@^1.5.2:
+ version "1.5.2"
+ resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
+
+async@^2.1.2, async@^2.1.4:
+ version "2.1.4"
+ resolved "https://registry.yarnpkg.com/async/-/async-2.1.4.tgz#2d2160c7788032e4dd6cbe2502f1f9a2c8f6cde4"
+ dependencies:
+ lodash "^4.14.0"
+
+async@~0.9.0:
+ version "0.9.2"
+ resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
+
+asynckit@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
+
+aws-sign2@~0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f"
+
+aws4@^1.2.1:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
+
+babel-code-frame@^6.16.0, babel-code-frame@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4"
+ dependencies:
+ chalk "^1.1.0"
+ esutils "^2.0.2"
+ js-tokens "^3.0.0"
+
+babel-core@^6.22.1, babel-core@^6.23.0:
+ version "6.23.1"
+ resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.23.1.tgz#c143cb621bb2f621710c220c5d579d15b8a442df"
+ dependencies:
+ babel-code-frame "^6.22.0"
+ babel-generator "^6.23.0"
+ babel-helpers "^6.23.0"
+ babel-messages "^6.23.0"
+ babel-register "^6.23.0"
+ babel-runtime "^6.22.0"
+ babel-template "^6.23.0"
+ babel-traverse "^6.23.1"
+ babel-types "^6.23.0"
+ babylon "^6.11.0"
+ convert-source-map "^1.1.0"
+ debug "^2.1.1"
+ json5 "^0.5.0"
+ lodash "^4.2.0"
+ minimatch "^3.0.2"
+ path-is-absolute "^1.0.0"
+ private "^0.1.6"
+ slash "^1.0.0"
+ source-map "^0.5.0"
+
+babel-generator@^6.18.0, babel-generator@^6.23.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.23.0.tgz#6b8edab956ef3116f79d8c84c5a3c05f32a74bc5"
+ dependencies:
+ babel-messages "^6.23.0"
+ babel-runtime "^6.22.0"
+ babel-types "^6.23.0"
+ detect-indent "^4.0.0"
+ jsesc "^1.3.0"
+ lodash "^4.2.0"
+ source-map "^0.5.0"
+ trim-right "^1.0.1"
+
+babel-helper-bindify-decorators@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.22.0.tgz#d7f5bc261275941ac62acfc4e20dacfb8a3fe952"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-traverse "^6.22.0"
+ babel-types "^6.22.0"
+
+babel-helper-builder-binary-assignment-operator-visitor@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.22.0.tgz#29df56be144d81bdeac08262bfa41d2c5e91cdcd"
+ dependencies:
+ babel-helper-explode-assignable-expression "^6.22.0"
+ babel-runtime "^6.22.0"
+ babel-types "^6.22.0"
+
+babel-helper-call-delegate@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.22.0.tgz#119921b56120f17e9dae3f74b4f5cc7bcc1b37ef"
+ dependencies:
+ babel-helper-hoist-variables "^6.22.0"
+ babel-runtime "^6.22.0"
+ babel-traverse "^6.22.0"
+ babel-types "^6.22.0"
+
+babel-helper-define-map@^6.23.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.23.0.tgz#1444f960c9691d69a2ced6a205315f8fd00804e7"
+ dependencies:
+ babel-helper-function-name "^6.23.0"
+ babel-runtime "^6.22.0"
+ babel-types "^6.23.0"
+ lodash "^4.2.0"
+
+babel-helper-explode-assignable-expression@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.22.0.tgz#c97bf76eed3e0bae4048121f2b9dae1a4e7d0478"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-traverse "^6.22.0"
+ babel-types "^6.22.0"
+
+babel-helper-explode-class@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-helper-explode-class/-/babel-helper-explode-class-6.22.0.tgz#646304924aa6388a516843ba7f1855ef8dfeb69b"
+ dependencies:
+ babel-helper-bindify-decorators "^6.22.0"
+ babel-runtime "^6.22.0"
+ babel-traverse "^6.22.0"
+ babel-types "^6.22.0"
+
+babel-helper-function-name@^6.22.0, babel-helper-function-name@^6.23.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.23.0.tgz#25742d67175c8903dbe4b6cb9d9e1fcb8dcf23a6"
+ dependencies:
+ babel-helper-get-function-arity "^6.22.0"
+ babel-runtime "^6.22.0"
+ babel-template "^6.23.0"
+ babel-traverse "^6.23.0"
+ babel-types "^6.23.0"
+
+babel-helper-get-function-arity@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.22.0.tgz#0beb464ad69dc7347410ac6ade9f03a50634f5ce"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-types "^6.22.0"
+
+babel-helper-hoist-variables@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.22.0.tgz#3eacbf731d80705845dd2e9718f600cfb9b4ba72"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-types "^6.22.0"
+
+babel-helper-optimise-call-expression@^6.23.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.23.0.tgz#f3ee7eed355b4282138b33d02b78369e470622f5"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-types "^6.23.0"
+
+babel-helper-regex@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.22.0.tgz#79f532be1647b1f0ee3474b5f5c3da58001d247d"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-types "^6.22.0"
+ lodash "^4.2.0"
+
+babel-helper-remap-async-to-generator@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.22.0.tgz#2186ae73278ed03b8b15ced089609da981053383"
+ dependencies:
+ babel-helper-function-name "^6.22.0"
+ babel-runtime "^6.22.0"
+ babel-template "^6.22.0"
+ babel-traverse "^6.22.0"
+ babel-types "^6.22.0"
+
+babel-helper-replace-supers@^6.22.0, babel-helper-replace-supers@^6.23.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.23.0.tgz#eeaf8ad9b58ec4337ca94223bacdca1f8d9b4bfd"
+ dependencies:
+ babel-helper-optimise-call-expression "^6.23.0"
+ babel-messages "^6.23.0"
+ babel-runtime "^6.22.0"
+ babel-template "^6.23.0"
+ babel-traverse "^6.23.0"
+ babel-types "^6.23.0"
+
+babel-helpers@^6.23.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.23.0.tgz#4f8f2e092d0b6a8808a4bde79c27f1e2ecf0d992"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-template "^6.23.0"
+
+babel-loader@^6.2.10:
+ version "6.2.10"
+ resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-6.2.10.tgz#adefc2b242320cd5d15e65b31cea0e8b1b02d4b0"
+ dependencies:
+ find-cache-dir "^0.1.1"
+ loader-utils "^0.2.11"
+ mkdirp "^0.5.1"
+ object-assign "^4.0.1"
+
+babel-messages@^6.23.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-check-es2015-constants@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-istanbul@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.0.0.tgz#36bde8fbef4837e5ff0366531a2beabd7b1ffa10"
+ dependencies:
+ find-up "^2.1.0"
+ istanbul-lib-instrument "^1.4.2"
+ test-exclude "^4.0.0"
+
+babel-plugin-syntax-async-functions@^6.8.0:
+ version "6.13.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95"
+
+babel-plugin-syntax-async-generators@^6.5.0:
+ version "6.13.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz#6bc963ebb16eccbae6b92b596eb7f35c342a8b9a"
+
+babel-plugin-syntax-class-properties@^6.8.0:
+ version "6.13.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz#d7eb23b79a317f8543962c505b827c7d6cac27de"
+
+babel-plugin-syntax-decorators@^6.13.0:
+ version "6.13.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz#312563b4dbde3cc806cee3e416cceeaddd11ac0b"
+
+babel-plugin-syntax-dynamic-import@^6.18.0:
+ version "6.18.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz#8d6a26229c83745a9982a441051572caa179b1da"
+
+babel-plugin-syntax-exponentiation-operator@^6.8.0:
+ version "6.13.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de"
+
+babel-plugin-syntax-object-rest-spread@^6.8.0:
+ version "6.13.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
+
+babel-plugin-syntax-trailing-function-commas@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3"
+
+babel-plugin-transform-async-generator-functions@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.22.0.tgz#a720a98153a7596f204099cd5409f4b3c05bab46"
+ dependencies:
+ babel-helper-remap-async-to-generator "^6.22.0"
+ babel-plugin-syntax-async-generators "^6.5.0"
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-async-to-generator@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.22.0.tgz#194b6938ec195ad36efc4c33a971acf00d8cd35e"
+ dependencies:
+ babel-helper-remap-async-to-generator "^6.22.0"
+ babel-plugin-syntax-async-functions "^6.8.0"
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-class-properties@^6.22.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.23.0.tgz#187b747ee404399013563c993db038f34754ac3b"
+ dependencies:
+ babel-helper-function-name "^6.23.0"
+ babel-plugin-syntax-class-properties "^6.8.0"
+ babel-runtime "^6.22.0"
+ babel-template "^6.23.0"
+
+babel-plugin-transform-decorators@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-decorators/-/babel-plugin-transform-decorators-6.22.0.tgz#c03635b27a23b23b7224f49232c237a73988d27c"
+ dependencies:
+ babel-helper-explode-class "^6.22.0"
+ babel-plugin-syntax-decorators "^6.13.0"
+ babel-runtime "^6.22.0"
+ babel-template "^6.22.0"
+ babel-types "^6.22.0"
+
+babel-plugin-transform-es2015-arrow-functions@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-block-scoped-functions@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz#bbc51b49f964d70cb8d8e0b94e820246ce3a6141"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-block-scoping@^6.22.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.23.0.tgz#e48895cf0b375be148cd7c8879b422707a053b51"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-template "^6.23.0"
+ babel-traverse "^6.23.0"
+ babel-types "^6.23.0"
+ lodash "^4.2.0"
+
+babel-plugin-transform-es2015-classes@^6.22.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.23.0.tgz#49b53f326202a2fd1b3bbaa5e2edd8a4f78643c1"
+ dependencies:
+ babel-helper-define-map "^6.23.0"
+ babel-helper-function-name "^6.23.0"
+ babel-helper-optimise-call-expression "^6.23.0"
+ babel-helper-replace-supers "^6.23.0"
+ babel-messages "^6.23.0"
+ babel-runtime "^6.22.0"
+ babel-template "^6.23.0"
+ babel-traverse "^6.23.0"
+ babel-types "^6.23.0"
+
+babel-plugin-transform-es2015-computed-properties@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.22.0.tgz#7c383e9629bba4820c11b0425bdd6290f7f057e7"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-template "^6.22.0"
+
+babel-plugin-transform-es2015-destructuring@^6.22.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-duplicate-keys@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.22.0.tgz#672397031c21610d72dd2bbb0ba9fb6277e1c36b"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-types "^6.22.0"
+
+babel-plugin-transform-es2015-for-of@^6.22.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-function-name@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.22.0.tgz#f5fcc8b09093f9a23c76ac3d9e392c3ec4b77104"
+ dependencies:
+ babel-helper-function-name "^6.22.0"
+ babel-runtime "^6.22.0"
+ babel-types "^6.22.0"
+
+babel-plugin-transform-es2015-literals@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz#4f54a02d6cd66cf915280019a31d31925377ca2e"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-modules-amd@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.22.0.tgz#bf69cd34889a41c33d90dfb740e0091ccff52f21"
+ dependencies:
+ babel-plugin-transform-es2015-modules-commonjs "^6.22.0"
+ babel-runtime "^6.22.0"
+ babel-template "^6.22.0"
+
+babel-plugin-transform-es2015-modules-commonjs@^6.22.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.23.0.tgz#cba7aa6379fb7ec99250e6d46de2973aaffa7b92"
+ dependencies:
+ babel-plugin-transform-strict-mode "^6.22.0"
+ babel-runtime "^6.22.0"
+ babel-template "^6.23.0"
+ babel-types "^6.23.0"
+
+babel-plugin-transform-es2015-modules-systemjs@^6.22.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.23.0.tgz#ae3469227ffac39b0310d90fec73bfdc4f6317b0"
+ dependencies:
+ babel-helper-hoist-variables "^6.22.0"
+ babel-runtime "^6.22.0"
+ babel-template "^6.23.0"
+
+babel-plugin-transform-es2015-modules-umd@^6.22.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.23.0.tgz#8d284ae2e19ed8fe21d2b1b26d6e7e0fcd94f0f1"
+ dependencies:
+ babel-plugin-transform-es2015-modules-amd "^6.22.0"
+ babel-runtime "^6.22.0"
+ babel-template "^6.23.0"
+
+babel-plugin-transform-es2015-object-super@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.22.0.tgz#daa60e114a042ea769dd53fe528fc82311eb98fc"
+ dependencies:
+ babel-helper-replace-supers "^6.22.0"
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-parameters@^6.22.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.23.0.tgz#3a2aabb70c8af945d5ce386f1a4250625a83ae3b"
+ dependencies:
+ babel-helper-call-delegate "^6.22.0"
+ babel-helper-get-function-arity "^6.22.0"
+ babel-runtime "^6.22.0"
+ babel-template "^6.23.0"
+ babel-traverse "^6.23.0"
+ babel-types "^6.23.0"
+
+babel-plugin-transform-es2015-shorthand-properties@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.22.0.tgz#8ba776e0affaa60bff21e921403b8a652a2ff723"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-types "^6.22.0"
+
+babel-plugin-transform-es2015-spread@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-sticky-regex@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.22.0.tgz#ab316829e866ee3f4b9eb96939757d19a5bc4593"
+ dependencies:
+ babel-helper-regex "^6.22.0"
+ babel-runtime "^6.22.0"
+ babel-types "^6.22.0"
+
+babel-plugin-transform-es2015-template-literals@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz#a84b3450f7e9f8f1f6839d6d687da84bb1236d8d"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-typeof-symbol@^6.22.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-unicode-regex@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.22.0.tgz#8d9cc27e7ee1decfe65454fb986452a04a613d20"
+ dependencies:
+ babel-helper-regex "^6.22.0"
+ babel-runtime "^6.22.0"
+ regexpu-core "^2.0.0"
+
+babel-plugin-transform-exponentiation-operator@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.22.0.tgz#d57c8335281918e54ef053118ce6eb108468084d"
+ dependencies:
+ babel-helper-builder-binary-assignment-operator-visitor "^6.22.0"
+ babel-plugin-syntax-exponentiation-operator "^6.8.0"
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-object-rest-spread@^6.22.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.23.0.tgz#875d6bc9be761c58a2ae3feee5dc4895d8c7f921"
+ dependencies:
+ babel-plugin-syntax-object-rest-spread "^6.8.0"
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-regenerator@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.22.0.tgz#65740593a319c44522157538d690b84094617ea6"
+ dependencies:
+ regenerator-transform "0.9.8"
+
+babel-plugin-transform-strict-mode@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.22.0.tgz#e008df01340fdc87e959da65991b7e05970c8c7c"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-types "^6.22.0"
+
+babel-preset-es2015@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-preset-es2015/-/babel-preset-es2015-6.22.0.tgz#af5a98ecb35eb8af764ad8a5a05eb36dc4386835"
+ dependencies:
+ babel-plugin-check-es2015-constants "^6.22.0"
+ babel-plugin-transform-es2015-arrow-functions "^6.22.0"
+ babel-plugin-transform-es2015-block-scoped-functions "^6.22.0"
+ babel-plugin-transform-es2015-block-scoping "^6.22.0"
+ babel-plugin-transform-es2015-classes "^6.22.0"
+ babel-plugin-transform-es2015-computed-properties "^6.22.0"
+ babel-plugin-transform-es2015-destructuring "^6.22.0"
+ babel-plugin-transform-es2015-duplicate-keys "^6.22.0"
+ babel-plugin-transform-es2015-for-of "^6.22.0"
+ babel-plugin-transform-es2015-function-name "^6.22.0"
+ babel-plugin-transform-es2015-literals "^6.22.0"
+ babel-plugin-transform-es2015-modules-amd "^6.22.0"
+ babel-plugin-transform-es2015-modules-commonjs "^6.22.0"
+ babel-plugin-transform-es2015-modules-systemjs "^6.22.0"
+ babel-plugin-transform-es2015-modules-umd "^6.22.0"
+ babel-plugin-transform-es2015-object-super "^6.22.0"
+ babel-plugin-transform-es2015-parameters "^6.22.0"
+ babel-plugin-transform-es2015-shorthand-properties "^6.22.0"
+ babel-plugin-transform-es2015-spread "^6.22.0"
+ babel-plugin-transform-es2015-sticky-regex "^6.22.0"
+ babel-plugin-transform-es2015-template-literals "^6.22.0"
+ babel-plugin-transform-es2015-typeof-symbol "^6.22.0"
+ babel-plugin-transform-es2015-unicode-regex "^6.22.0"
+ babel-plugin-transform-regenerator "^6.22.0"
+
+babel-preset-stage-2@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-preset-stage-2/-/babel-preset-stage-2-6.22.0.tgz#ccd565f19c245cade394b21216df704a73b27c07"
+ dependencies:
+ babel-plugin-syntax-dynamic-import "^6.18.0"
+ babel-plugin-transform-class-properties "^6.22.0"
+ babel-plugin-transform-decorators "^6.22.0"
+ babel-preset-stage-3 "^6.22.0"
+
+babel-preset-stage-3@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-preset-stage-3/-/babel-preset-stage-3-6.22.0.tgz#a4e92bbace7456fafdf651d7a7657ee0bbca9c2e"
+ dependencies:
+ babel-plugin-syntax-trailing-function-commas "^6.22.0"
+ babel-plugin-transform-async-generator-functions "^6.22.0"
+ babel-plugin-transform-async-to-generator "^6.22.0"
+ babel-plugin-transform-exponentiation-operator "^6.22.0"
+ babel-plugin-transform-object-rest-spread "^6.22.0"
+
+babel-register@^6.23.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.23.0.tgz#c9aa3d4cca94b51da34826c4a0f9e08145d74ff3"
+ dependencies:
+ babel-core "^6.23.0"
+ babel-runtime "^6.22.0"
+ core-js "^2.4.0"
+ home-or-tmp "^2.0.0"
+ lodash "^4.2.0"
+ mkdirp "^0.5.1"
+ source-map-support "^0.4.2"
+
+babel-runtime@^6.18.0, babel-runtime@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.22.0.tgz#1cf8b4ac67c77a4ddb0db2ae1f74de52ac4ca611"
+ dependencies:
+ core-js "^2.4.0"
+ regenerator-runtime "^0.10.0"
+
+babel-template@^6.16.0, babel-template@^6.22.0, babel-template@^6.23.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.23.0.tgz#04d4f270adbb3aa704a8143ae26faa529238e638"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-traverse "^6.23.0"
+ babel-types "^6.23.0"
+ babylon "^6.11.0"
+ lodash "^4.2.0"
+
+babel-traverse@^6.18.0, babel-traverse@^6.22.0, babel-traverse@^6.23.0, babel-traverse@^6.23.1:
+ version "6.23.1"
+ resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.23.1.tgz#d3cb59010ecd06a97d81310065f966b699e14f48"
+ dependencies:
+ babel-code-frame "^6.22.0"
+ babel-messages "^6.23.0"
+ babel-runtime "^6.22.0"
+ babel-types "^6.23.0"
+ babylon "^6.15.0"
+ debug "^2.2.0"
+ globals "^9.0.0"
+ invariant "^2.2.0"
+ lodash "^4.2.0"
+
+babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.22.0, babel-types@^6.23.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.23.0.tgz#bb17179d7538bad38cd0c9e115d340f77e7e9acf"
+ dependencies:
+ babel-runtime "^6.22.0"
+ esutils "^2.0.2"
+ lodash "^4.2.0"
+ to-fast-properties "^1.0.1"
+
+babylon@^6.11.0, babylon@^6.13.0, babylon@^6.15.0:
+ version "6.15.0"
+ resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.15.0.tgz#ba65cfa1a80e1759b0e89fb562e27dccae70348e"
+
+backo2@1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947"
+
+balanced-match@^0.4.1:
+ version "0.4.2"
+ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838"
+
+base64-arraybuffer@0.1.5:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8"
+
+base64-js@^1.0.2:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.0.tgz#a39992d723584811982be5e290bb6a53d86700f1"
+
+base64id@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/base64id/-/base64id-1.0.0.tgz#47688cb99bb6804f0e06d3e763b1c32e57d8e6b6"
+
+batch@0.5.3:
+ version "0.5.3"
+ resolved "https://registry.yarnpkg.com/batch/-/batch-0.5.3.tgz#3f3414f380321743bfc1042f9a83ff1d5824d464"
+
+bcrypt-pbkdf@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d"
+ dependencies:
+ tweetnacl "^0.14.3"
+
+better-assert@~1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522"
+ dependencies:
+ callsite "1.0.0"
+
+big.js@^3.1.3:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.1.3.tgz#4cada2193652eb3ca9ec8e55c9015669c9806978"
+
+binary-extensions@^1.0.0:
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.8.0.tgz#48ec8d16df4377eae5fa5884682480af4d95c774"
+
+blob@0.0.4:
+ version "0.0.4"
+ resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.4.tgz#bcf13052ca54463f30f9fc7e95b9a47630a94921"
+
+block-stream@*:
+ version "0.0.9"
+ resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a"
+ dependencies:
+ inherits "~2.0.0"
+
+bluebird@^3.3.0:
+ version "3.4.7"
+ resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3"
+
+bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
+ version "4.11.6"
+ resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215"
+
+body-parser@^1.12.4:
+ version "1.16.0"
+ resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.16.0.tgz#924a5e472c6229fb9d69b85a20d5f2532dec788b"
+ dependencies:
+ bytes "2.4.0"
+ content-type "~1.0.2"
+ debug "2.6.0"
+ depd "~1.1.0"
+ http-errors "~1.5.1"
+ iconv-lite "0.4.15"
+ on-finished "~2.3.0"
+ qs "6.2.1"
+ raw-body "~2.2.0"
+ type-is "~1.6.14"
+
+boom@2.x.x:
+ version "2.10.1"
+ resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f"
+ dependencies:
+ hoek "2.x.x"
+
+bootstrap-sass@^3.3.6:
+ version "3.3.6"
+ resolved "https://registry.yarnpkg.com/bootstrap-sass/-/bootstrap-sass-3.3.6.tgz#363b0d300e868d3e70134c1a742bb17288444fd1"
+
+brace-expansion@^1.0.0:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.6.tgz#7197d7eaa9b87e648390ea61fc66c84427420df9"
+ dependencies:
+ balanced-match "^0.4.1"
+ concat-map "0.0.1"
+
+braces@^0.1.2:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/braces/-/braces-0.1.5.tgz#c085711085291d8b75fdd74eab0f8597280711e6"
+ dependencies:
+ expand-range "^0.1.0"
+
+braces@^1.8.2:
+ version "1.8.5"
+ resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7"
+ dependencies:
+ expand-range "^1.8.1"
+ preserve "^0.2.0"
+ repeat-element "^1.1.2"
+
+brorand@^1.0.1:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.0.7.tgz#6677fa5e4901bdbf9c9ec2a748e28dca407a9bfc"
+
+browserify-aes@^1.0.0, browserify-aes@^1.0.4:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.0.6.tgz#5e7725dbdef1fd5930d4ebab48567ce451c48a0a"
+ dependencies:
+ buffer-xor "^1.0.2"
+ cipher-base "^1.0.0"
+ create-hash "^1.1.0"
+ evp_bytestokey "^1.0.0"
+ inherits "^2.0.1"
+
+browserify-cipher@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.0.tgz#9988244874bf5ed4e28da95666dcd66ac8fc363a"
+ dependencies:
+ browserify-aes "^1.0.4"
+ browserify-des "^1.0.0"
+ evp_bytestokey "^1.0.0"
+
+browserify-des@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.0.tgz#daa277717470922ed2fe18594118a175439721dd"
+ dependencies:
+ cipher-base "^1.0.1"
+ des.js "^1.0.0"
+ inherits "^2.0.1"
+
+browserify-rsa@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524"
+ dependencies:
+ bn.js "^4.1.0"
+ randombytes "^2.0.1"
+
+browserify-sign@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.0.tgz#10773910c3c206d5420a46aad8694f820b85968f"
+ dependencies:
+ bn.js "^4.1.1"
+ browserify-rsa "^4.0.0"
+ create-hash "^1.1.0"
+ create-hmac "^1.1.2"
+ elliptic "^6.0.0"
+ inherits "^2.0.1"
+ parse-asn1 "^5.0.0"
+
+browserify-zlib@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.1.4.tgz#bb35f8a519f600e0fa6b8485241c979d0141fb2d"
+ dependencies:
+ pako "~0.2.0"
+
+buffer-shims@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51"
+
+buffer-xor@^1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
+
+buffer@^4.3.0:
+ version "4.9.1"
+ resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298"
+ dependencies:
+ base64-js "^1.0.2"
+ ieee754 "^1.1.4"
+ isarray "^1.0.0"
+
+builtin-modules@^1.0.0, builtin-modules@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
+
+builtin-status-codes@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
+
+bytes@2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.3.0.tgz#d5b680a165b6201739acb611542aabc2d8ceb070"
+
+bytes@2.4.0:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.4.0.tgz#7d97196f9d5baf7f6935e25985549edd2a6c2339"
+
+caller-path@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f"
+ dependencies:
+ callsites "^0.2.0"
+
+callsite@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20"
+
+callsites@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca"
+
+camelcase@^1.0.2:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39"
+
+camelcase@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a"
+
+caseless@~0.11.0:
+ version "0.11.0"
+ resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7"
+
+center-align@^0.1.1:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad"
+ dependencies:
+ align-text "^0.1.3"
+ lazy-cache "^1.0.3"
+
+chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
+ dependencies:
+ ansi-styles "^2.2.1"
+ escape-string-regexp "^1.0.2"
+ has-ansi "^2.0.0"
+ strip-ansi "^3.0.0"
+ supports-color "^2.0.0"
+
+chokidar@^1.4.1, chokidar@^1.4.3, chokidar@^1.6.0:
+ version "1.6.1"
+ resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.6.1.tgz#2f4447ab5e96e50fb3d789fd90d4c72e0e4c70c2"
+ dependencies:
+ anymatch "^1.3.0"
+ async-each "^1.0.0"
+ glob-parent "^2.0.0"
+ inherits "^2.0.1"
+ is-binary-path "^1.0.0"
+ is-glob "^2.0.0"
+ path-is-absolute "^1.0.0"
+ readdirp "^2.0.0"
+ optionalDependencies:
+ fsevents "^1.0.0"
+
+cipher-base@^1.0.0, cipher-base@^1.0.1:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.3.tgz#eeabf194419ce900da3018c207d212f2a6df0a07"
+ dependencies:
+ inherits "^2.0.1"
+
+circular-json@^0.3.1:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d"
+
+cli-cursor@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987"
+ dependencies:
+ restore-cursor "^1.0.1"
+
+cli-width@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.1.0.tgz#b234ca209b29ef66fc518d9b98d5847b00edf00a"
+
+cliui@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1"
+ dependencies:
+ center-align "^0.1.1"
+ right-align "^0.1.1"
+ wordwrap "0.0.2"
+
+cliui@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
+ dependencies:
+ string-width "^1.0.1"
+ strip-ansi "^3.0.1"
+ wrap-ansi "^2.0.0"
+
+clone@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.2.tgz#260b7a99ebb1edfe247538175f783243cb19d149"
+
+co@^4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
+
+code-point-at@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
+
+colors@^1.1.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
+
+combine-lists@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/combine-lists/-/combine-lists-1.0.1.tgz#458c07e09e0d900fc28b70a3fec2dacd1d2cb7f6"
+ dependencies:
+ lodash "^4.5.0"
+
+combined-stream@^1.0.5, combined-stream@~1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009"
+ dependencies:
+ delayed-stream "~1.0.0"
+
+commander@^2.8.1, commander@^2.9.0:
+ version "2.9.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4"
+ dependencies:
+ graceful-readlink ">= 1.0.0"
+
+commondir@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
+
+component-bind@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1"
+
+component-emitter@1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.1.2.tgz#296594f2753daa63996d2af08d15a95116c9aec3"
+
+component-emitter@1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
+
+component-inherit@0.0.3:
+ version "0.0.3"
+ resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143"
+
+compressible@~2.0.8:
+ version "2.0.9"
+ resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.9.tgz#6daab4e2b599c2770dd9e21e7a891b1c5a755425"
+ dependencies:
+ mime-db ">= 1.24.0 < 2"
+
+compression-webpack-plugin@^0.3.2:
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/compression-webpack-plugin/-/compression-webpack-plugin-0.3.2.tgz#1edfb0e749d7366d3e701670c463359b2c0cf704"
+ dependencies:
+ async "0.2.x"
+ webpack-sources "^0.1.0"
+ optionalDependencies:
+ node-zopfli "^2.0.0"
+
+compression@^1.5.2:
+ version "1.6.2"
+ resolved "https://registry.yarnpkg.com/compression/-/compression-1.6.2.tgz#cceb121ecc9d09c52d7ad0c3350ea93ddd402bc3"
+ dependencies:
+ accepts "~1.3.3"
+ bytes "2.3.0"
+ compressible "~2.0.8"
+ debug "~2.2.0"
+ on-headers "~1.0.1"
+ vary "~1.1.0"
+
+concat-map@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
+
+concat-stream@1.5.0:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.5.0.tgz#53f7d43c51c5e43f81c8fdd03321c631be68d611"
+ dependencies:
+ inherits "~2.0.1"
+ readable-stream "~2.0.0"
+ typedarray "~0.0.5"
+
+concat-stream@^1.4.6:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7"
+ dependencies:
+ inherits "^2.0.3"
+ readable-stream "^2.2.2"
+ typedarray "^0.0.6"
+
+connect-history-api-fallback@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.3.0.tgz#e51d17f8f0ef0db90a64fdb47de3051556e9f169"
+
+connect@^3.3.5:
+ version "3.5.0"
+ resolved "https://registry.yarnpkg.com/connect/-/connect-3.5.0.tgz#b357525a0b4c1f50599cd983e1d9efeea9677198"
+ dependencies:
+ debug "~2.2.0"
+ finalhandler "0.5.0"
+ parseurl "~1.3.1"
+ utils-merge "1.0.0"
+
+console-browserify@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10"
+ dependencies:
+ date-now "^0.1.4"
+
+console-control-strings@^1.0.0, console-control-strings@~1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
+
+constants-browserify@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
+
+contains-path@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a"
+
+content-disposition@0.5.2:
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
+
+content-type@~1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed"
+
+convert-source-map@^1.1.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.3.0.tgz#e9f3e9c6e2728efc2676696a70eb382f73106a67"
+
+cookie-signature@1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
+
+cookie@0.3.1:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
+
+core-js@^2.2.0, core-js@^2.4.0:
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e"
+
+core-util-is@~1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
+
+create-ecdh@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.0.tgz#888c723596cdf7612f6498233eebd7a35301737d"
+ dependencies:
+ bn.js "^4.1.0"
+ elliptic "^6.0.0"
+
+create-hash@^1.1.0, create-hash@^1.1.1:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.1.2.tgz#51210062d7bb7479f6c65bb41a92208b1d61abad"
+ dependencies:
+ cipher-base "^1.0.1"
+ inherits "^2.0.1"
+ ripemd160 "^1.0.0"
+ sha.js "^2.3.6"
+
+create-hmac@^1.1.0, create-hmac@^1.1.2:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.4.tgz#d3fb4ba253eb8b3f56e39ea2fbcb8af747bd3170"
+ dependencies:
+ create-hash "^1.1.0"
+ inherits "^2.0.1"
+
+cryptiles@2.x.x:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8"
+ dependencies:
+ boom "2.x.x"
+
+crypto-browserify@^3.11.0:
+ version "3.11.0"
+ resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.11.0.tgz#3652a0906ab9b2a7e0c3ce66a408e957a2485522"
+ dependencies:
+ browserify-cipher "^1.0.0"
+ browserify-sign "^4.0.0"
+ create-ecdh "^4.0.0"
+ create-hash "^1.1.0"
+ create-hmac "^1.1.0"
+ diffie-hellman "^5.0.0"
+ inherits "^2.0.1"
+ pbkdf2 "^3.0.3"
+ public-encrypt "^4.0.0"
+ randombytes "^2.0.0"
+
+custom-event@~1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425"
+
+d3@^3.5.11:
+ version "3.5.11"
+ resolved "https://registry.yarnpkg.com/d3/-/d3-3.5.11.tgz#d130750eed0554db70e8432102f920a12407b69c"
+
+d@^0.1.1, d@~0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/d/-/d-0.1.1.tgz#da184c535d18d8ee7ba2aa229b914009fae11309"
+ dependencies:
+ es5-ext "~0.10.2"
+
+dashdash@^1.12.0:
+ version "1.14.1"
+ resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
+ dependencies:
+ assert-plus "^1.0.0"
+
+date-now@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
+
+debug@0.7.4:
+ version "0.7.4"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-0.7.4.tgz#06e1ea8082c2cb14e39806e22e2f6f757f92af39"
+
+debug@2.2.0, debug@~2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da"
+ dependencies:
+ ms "0.7.1"
+
+debug@2.3.3:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-2.3.3.tgz#40c453e67e6e13c901ddec317af8986cda9eff8c"
+ dependencies:
+ ms "0.7.2"
+
+debug@2.6.0, debug@^2.1.1, debug@^2.2.0:
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.0.tgz#bc596bcabe7617f11d9fa15361eded5608b8499b"
+ dependencies:
+ ms "0.7.2"
+
+decamelize@^1.0.0, decamelize@^1.1.1:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
+
+deep-extend@~0.4.0:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.1.tgz#efe4113d08085f4e6f9687759810f807469e2253"
+
+deep-is@~0.1.3:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
+
+default-require-extensions@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-1.0.0.tgz#f37ea15d3e13ffd9b437d33e1a75b5fb97874cb8"
+ dependencies:
+ strip-bom "^2.0.0"
+
+defaults@^1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d"
+ dependencies:
+ clone "^1.0.2"
+
+del@^2.0.2:
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8"
+ dependencies:
+ globby "^5.0.0"
+ is-path-cwd "^1.0.0"
+ is-path-in-cwd "^1.0.0"
+ object-assign "^4.0.1"
+ pify "^2.0.0"
+ pinkie-promise "^2.0.0"
+ rimraf "^2.2.8"
+
+delayed-stream@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
+
+delegates@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
+
+depd@~1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3"
+
+des.js@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc"
+ dependencies:
+ inherits "^2.0.1"
+ minimalistic-assert "^1.0.0"
+
+destroy@~1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
+
+detect-indent@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208"
+ dependencies:
+ repeating "^2.0.0"
+
+di@^0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c"
+
+diffie-hellman@^5.0.0:
+ version "5.0.2"
+ resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.2.tgz#b5835739270cfe26acf632099fded2a07f209e5e"
+ dependencies:
+ bn.js "^4.1.0"
+ miller-rabin "^4.0.0"
+ randombytes "^2.0.0"
+
+doctrine@1.5.0, doctrine@^1.2.2:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa"
+ dependencies:
+ esutils "^2.0.2"
+ isarray "^1.0.0"
+
+dom-serialize@^2.2.0:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/dom-serialize/-/dom-serialize-2.2.1.tgz#562ae8999f44be5ea3076f5419dcd59eb43ac95b"
+ dependencies:
+ custom-event "~1.0.0"
+ ent "~2.2.0"
+ extend "^3.0.0"
+ void-elements "^2.0.0"
+
+domain-browser@^1.1.1:
+ version "1.1.7"
+ resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc"
+
+dropzone@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/dropzone/-/dropzone-4.2.0.tgz#fbe7acbb9918e0706489072ef663effeef8a79f3"
+
+duplexer@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1"
+
+ecc-jsbn@~0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505"
+ dependencies:
+ jsbn "~0.1.0"
+
+ee-first@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
+
+ejs@^2.5.5:
+ version "2.5.6"
+ resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.6.tgz#479636bfa3fe3b1debd52087f0acb204b4f19c88"
+
+elliptic@^6.0.0:
+ version "6.3.3"
+ resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.3.3.tgz#5482d9646d54bcb89fd7d994fc9e2e9568876e3f"
+ dependencies:
+ bn.js "^4.4.0"
+ brorand "^1.0.1"
+ hash.js "^1.0.0"
+ inherits "^2.0.1"
+
+emojis-list@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
+
+encodeurl@~1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20"
+
+engine.io-client@1.8.2:
+ version "1.8.2"
+ resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-1.8.2.tgz#c38767547f2a7d184f5752f6f0ad501006703766"
+ dependencies:
+ component-emitter "1.2.1"
+ component-inherit "0.0.3"
+ debug "2.3.3"
+ engine.io-parser "1.3.2"
+ has-cors "1.1.0"
+ indexof "0.0.1"
+ parsejson "0.0.3"
+ parseqs "0.0.5"
+ parseuri "0.0.5"
+ ws "1.1.1"
+ xmlhttprequest-ssl "1.5.3"
+ yeast "0.1.2"
+
+engine.io-parser@1.3.2:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-1.3.2.tgz#937b079f0007d0893ec56d46cb220b8cb435220a"
+ dependencies:
+ after "0.8.2"
+ arraybuffer.slice "0.0.6"
+ base64-arraybuffer "0.1.5"
+ blob "0.0.4"
+ has-binary "0.1.7"
+ wtf-8 "1.0.0"
+
+engine.io@1.8.2:
+ version "1.8.2"
+ resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-1.8.2.tgz#6b59be730b348c0125b0a4589de1c355abcf7a7e"
+ dependencies:
+ accepts "1.3.3"
+ base64id "1.0.0"
+ cookie "0.3.1"
+ debug "2.3.3"
+ engine.io-parser "1.3.2"
+ ws "1.1.1"
+
+enhanced-resolve@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.1.0.tgz#9f4b626f577245edcf4b2ad83d86e17f4f421dec"
+ dependencies:
+ graceful-fs "^4.1.2"
+ memory-fs "^0.4.0"
+ object-assign "^4.0.1"
+ tapable "^0.2.5"
+
+enhanced-resolve@~0.9.0:
+ version "0.9.1"
+ resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz#4d6e689b3725f86090927ccc86cd9f1635b89e2e"
+ dependencies:
+ graceful-fs "^4.1.2"
+ memory-fs "^0.2.0"
+ tapable "^0.1.8"
+
+ent@~2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d"
+
+errno@^0.1.3:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.4.tgz#b896e23a9e5e8ba33871fc996abd3635fc9a1c7d"
+ dependencies:
+ prr "~0.0.0"
+
+error-ex@^1.2.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.0.tgz#e67b43f3e82c96ea3a584ffee0b9fc3325d802d9"
+ dependencies:
+ is-arrayish "^0.2.1"
+
+es5-ext@^0.10.7, es5-ext@^0.10.8, es5-ext@~0.10.11, es5-ext@~0.10.2, es5-ext@~0.10.7:
+ version "0.10.12"
+ resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.12.tgz#aa84641d4db76b62abba5e45fd805ecbab140047"
+ dependencies:
+ es6-iterator "2"
+ es6-symbol "~3.1"
+
+es6-iterator@2:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.0.tgz#bd968567d61635e33c0b80727613c9cb4b096bac"
+ dependencies:
+ d "^0.1.1"
+ es5-ext "^0.10.7"
+ es6-symbol "3"
+
+es6-map@^0.1.3:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.4.tgz#a34b147be224773a4d7da8072794cefa3632b897"
+ dependencies:
+ d "~0.1.1"
+ es5-ext "~0.10.11"
+ es6-iterator "2"
+ es6-set "~0.1.3"
+ es6-symbol "~3.1.0"
+ event-emitter "~0.3.4"
+
+es6-promise@^4.0.5, es6-promise@~4.0.3:
+ version "4.0.5"
+ resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.0.5.tgz#7882f30adde5b240ccfa7f7d78c548330951ae42"
+
+es6-set@~0.1.3:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.4.tgz#9516b6761c2964b92ff479456233a247dc707ce8"
+ dependencies:
+ d "~0.1.1"
+ es5-ext "~0.10.11"
+ es6-iterator "2"
+ es6-symbol "3"
+ event-emitter "~0.3.4"
+
+es6-symbol@3, es6-symbol@~3.1, es6-symbol@~3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.0.tgz#94481c655e7a7cad82eba832d97d5433496d7ffa"
+ dependencies:
+ d "~0.1.1"
+ es5-ext "~0.10.11"
+
+es6-weak-map@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.1.tgz#0d2bbd8827eb5fb4ba8f97fbfea50d43db21ea81"
+ dependencies:
+ d "^0.1.1"
+ es5-ext "^0.10.8"
+ es6-iterator "2"
+ es6-symbol "3"
+
+escape-html@~1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
+
+escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
+
+escodegen@1.8.x:
+ version "1.8.1"
+ resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.8.1.tgz#5a5b53af4693110bebb0867aa3430dd3b70a1018"
+ dependencies:
+ esprima "^2.7.1"
+ estraverse "^1.9.1"
+ esutils "^2.0.2"
+ optionator "^0.8.1"
+ optionalDependencies:
+ source-map "~0.2.0"
+
+escope@^3.6.0:
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3"
+ dependencies:
+ es6-map "^0.1.3"
+ es6-weak-map "^2.0.1"
+ esrecurse "^4.1.0"
+ estraverse "^4.1.1"
+
+eslint-config-airbnb-base@^10.0.1:
+ version "10.0.1"
+ resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-10.0.1.tgz#f17d4e52992c1d45d1b7713efbcd5ecd0e7e0506"
+
+eslint-import-resolver-node@^0.2.0:
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.2.3.tgz#5add8106e8c928db2cba232bcd9efa846e3da16c"
+ dependencies:
+ debug "^2.2.0"
+ object-assign "^4.0.1"
+ resolve "^1.1.6"
+
+eslint-import-resolver-webpack@^0.8.1:
+ version "0.8.1"
+ resolved "https://registry.yarnpkg.com/eslint-import-resolver-webpack/-/eslint-import-resolver-webpack-0.8.1.tgz#c7f8b4d5bd3c5b489457e5728c5db1c4ffbac9aa"
+ dependencies:
+ array-find "^1.0.0"
+ debug "^2.2.0"
+ enhanced-resolve "~0.9.0"
+ find-root "^0.1.1"
+ has "^1.0.1"
+ interpret "^1.0.0"
+ is-absolute "^0.2.3"
+ lodash.get "^3.7.0"
+ node-libs-browser "^1.0.0"
+ resolve "^1.2.0"
+ semver "^5.3.0"
+
+eslint-module-utils@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.0.0.tgz#a6f8c21d901358759cdc35dbac1982ae1ee58bce"
+ dependencies:
+ debug "2.2.0"
+ pkg-dir "^1.0.0"
+
+eslint-plugin-filenames@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-filenames/-/eslint-plugin-filenames-1.1.0.tgz#bb925218ab25b1aad1c622cfa9cb8f43cc03a4ff"
+ dependencies:
+ lodash.camelcase "4.1.1"
+ lodash.kebabcase "4.0.1"
+ lodash.snakecase "4.0.1"
+
+eslint-plugin-import@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.2.0.tgz#72ba306fad305d67c4816348a4699a4229ac8b4e"
+ dependencies:
+ builtin-modules "^1.1.1"
+ contains-path "^0.1.0"
+ debug "^2.2.0"
+ doctrine "1.5.0"
+ eslint-import-resolver-node "^0.2.0"
+ eslint-module-utils "^2.0.0"
+ has "^1.0.1"
+ lodash.cond "^4.3.0"
+ minimatch "^3.0.3"
+ pkg-up "^1.0.0"
+
+eslint-plugin-jasmine@^2.1.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-jasmine/-/eslint-plugin-jasmine-2.2.0.tgz#7135879383c39a667c721d302b9f20f0389543de"
+
+eslint@^3.10.1:
+ version "3.15.0"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-3.15.0.tgz#bdcc6a6c5ffe08160e7b93c066695362a91e30f2"
+ dependencies:
+ babel-code-frame "^6.16.0"
+ chalk "^1.1.3"
+ concat-stream "^1.4.6"
+ debug "^2.1.1"
+ doctrine "^1.2.2"
+ escope "^3.6.0"
+ espree "^3.4.0"
+ estraverse "^4.2.0"
+ esutils "^2.0.2"
+ file-entry-cache "^2.0.0"
+ glob "^7.0.3"
+ globals "^9.14.0"
+ ignore "^3.2.0"
+ imurmurhash "^0.1.4"
+ inquirer "^0.12.0"
+ is-my-json-valid "^2.10.0"
+ is-resolvable "^1.0.0"
+ js-yaml "^3.5.1"
+ json-stable-stringify "^1.0.0"
+ levn "^0.3.0"
+ lodash "^4.0.0"
+ mkdirp "^0.5.0"
+ natural-compare "^1.4.0"
+ optionator "^0.8.2"
+ path-is-inside "^1.0.1"
+ pluralize "^1.2.1"
+ progress "^1.1.8"
+ require-uncached "^1.0.2"
+ shelljs "^0.7.5"
+ strip-bom "^3.0.0"
+ strip-json-comments "~2.0.1"
+ table "^3.7.8"
+ text-table "~0.2.0"
+ user-home "^2.0.0"
+
+espree@^3.4.0:
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/espree/-/espree-3.4.0.tgz#41656fa5628e042878025ef467e78f125cb86e1d"
+ dependencies:
+ acorn "4.0.4"
+ acorn-jsx "^3.0.0"
+
+esprima@2.7.x, esprima@^2.7.1:
+ version "2.7.3"
+ resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581"
+
+esprima@^3.1.1:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
+
+esrecurse@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.1.0.tgz#4713b6536adf7f2ac4f327d559e7756bff648220"
+ dependencies:
+ estraverse "~4.1.0"
+ object-assign "^4.0.1"
+
+estraverse@^1.9.1:
+ version "1.9.3"
+ resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44"
+
+estraverse@^4.1.1, estraverse@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
+
+estraverse@~4.1.0:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.1.1.tgz#f6caca728933a850ef90661d0e17982ba47111a2"
+
+esutils@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
+
+etag@~1.7.0:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/etag/-/etag-1.7.0.tgz#03d30b5f67dd6e632d2945d30d6652731a34d5d8"
+
+event-emitter@~0.3.4:
+ version "0.3.4"
+ resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.4.tgz#8d63ddfb4cfe1fae3b32ca265c4c720222080bb5"
+ dependencies:
+ d "~0.1.1"
+ es5-ext "~0.10.7"
+
+eventemitter3@1.x.x:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508"
+
+events@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
+
+eventsource@~0.1.6:
+ version "0.1.6"
+ resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-0.1.6.tgz#0acede849ed7dd1ccc32c811bb11b944d4f29232"
+ dependencies:
+ original ">=0.0.5"
+
+evp_bytestokey@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.0.tgz#497b66ad9fef65cd7c08a6180824ba1476b66e53"
+ dependencies:
+ create-hash "^1.1.1"
+
+exit-hook@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8"
+
+expand-braces@^0.1.1:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/expand-braces/-/expand-braces-0.1.2.tgz#488b1d1d2451cb3d3a6b192cfc030f44c5855fea"
+ dependencies:
+ array-slice "^0.2.3"
+ array-unique "^0.2.1"
+ braces "^0.1.2"
+
+expand-brackets@^0.1.4:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b"
+ dependencies:
+ is-posix-bracket "^0.1.0"
+
+expand-range@^0.1.0:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-0.1.1.tgz#4cb8eda0993ca56fa4f41fc42f3cbb4ccadff044"
+ dependencies:
+ is-number "^0.1.1"
+ repeat-string "^0.2.2"
+
+expand-range@^1.8.1:
+ version "1.8.2"
+ resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337"
+ dependencies:
+ fill-range "^2.1.0"
+
+express@^4.13.3, express@^4.14.1:
+ version "4.14.1"
+ resolved "https://registry.yarnpkg.com/express/-/express-4.14.1.tgz#646c237f766f148c2120aff073817b9e4d7e0d33"
+ dependencies:
+ accepts "~1.3.3"
+ array-flatten "1.1.1"
+ content-disposition "0.5.2"
+ content-type "~1.0.2"
+ cookie "0.3.1"
+ cookie-signature "1.0.6"
+ debug "~2.2.0"
+ depd "~1.1.0"
+ encodeurl "~1.0.1"
+ escape-html "~1.0.3"
+ etag "~1.7.0"
+ finalhandler "0.5.1"
+ fresh "0.3.0"
+ merge-descriptors "1.0.1"
+ methods "~1.1.2"
+ on-finished "~2.3.0"
+ parseurl "~1.3.1"
+ path-to-regexp "0.1.7"
+ proxy-addr "~1.1.3"
+ qs "6.2.0"
+ range-parser "~1.2.0"
+ send "0.14.2"
+ serve-static "~1.11.2"
+ type-is "~1.6.14"
+ utils-merge "1.0.0"
+ vary "~1.1.0"
+
+extend@^3.0.0, extend@~3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.0.tgz#5a474353b9f3353ddd8176dfd37b91c83a46f1d4"
+
+extglob@^0.3.1:
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1"
+ dependencies:
+ is-extglob "^1.0.0"
+
+extract-zip@~1.5.0:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.5.0.tgz#92ccf6d81ef70a9fa4c1747114ccef6d8688a6c4"
+ dependencies:
+ concat-stream "1.5.0"
+ debug "0.7.4"
+ mkdirp "0.5.0"
+ yauzl "2.4.1"
+
+extsprintf@1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550"
+
+fast-levenshtein@~2.0.4:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
+
+faye-websocket@^0.10.0:
+ version "0.10.0"
+ resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4"
+ dependencies:
+ websocket-driver ">=0.5.1"
+
+faye-websocket@~0.11.0:
+ version "0.11.1"
+ resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.1.tgz#f0efe18c4f56e4f40afc7e06c719fd5ee6188f38"
+ dependencies:
+ websocket-driver ">=0.5.1"
+
+fd-slicer@~1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65"
+ dependencies:
+ pend "~1.2.0"
+
+figures@^1.3.5:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
+ dependencies:
+ escape-string-regexp "^1.0.5"
+ object-assign "^4.1.0"
+
+file-entry-cache@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361"
+ dependencies:
+ flat-cache "^1.2.1"
+ object-assign "^4.0.1"
+
+filename-regex@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.0.tgz#996e3e80479b98b9897f15a8a58b3d084e926775"
+
+fileset@^2.0.2:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/fileset/-/fileset-2.0.3.tgz#8e7548a96d3cc2327ee5e674168723a333bba2a0"
+ dependencies:
+ glob "^7.0.3"
+ minimatch "^3.0.3"
+
+filesize@^3.5.4:
+ version "3.5.4"
+ resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.5.4.tgz#742fc7fb6aef4ee3878682600c22f840731e1fda"
+
+fill-range@^2.1.0:
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723"
+ dependencies:
+ is-number "^2.1.0"
+ isobject "^2.0.0"
+ randomatic "^1.1.3"
+ repeat-element "^1.1.2"
+ repeat-string "^1.5.2"
+
+finalhandler@0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-0.5.0.tgz#e9508abece9b6dba871a6942a1d7911b91911ac7"
+ dependencies:
+ debug "~2.2.0"
+ escape-html "~1.0.3"
+ on-finished "~2.3.0"
+ statuses "~1.3.0"
+ unpipe "~1.0.0"
+
+finalhandler@0.5.1:
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-0.5.1.tgz#2c400d8d4530935bc232549c5fa385ec07de6fcd"
+ dependencies:
+ debug "~2.2.0"
+ escape-html "~1.0.3"
+ on-finished "~2.3.0"
+ statuses "~1.3.1"
+ unpipe "~1.0.0"
+
+find-cache-dir@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-0.1.1.tgz#c8defae57c8a52a8a784f9e31c57c742e993a0b9"
+ dependencies:
+ commondir "^1.0.1"
+ mkdirp "^0.5.1"
+ pkg-dir "^1.0.0"
+
+find-root@^0.1.1:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/find-root/-/find-root-0.1.2.tgz#98d2267cff1916ccaf2743b3a0eea81d79d7dcd1"
+
+find-up@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
+ dependencies:
+ path-exists "^2.0.0"
+ pinkie-promise "^2.0.0"
+
+find-up@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
+ dependencies:
+ locate-path "^2.0.0"
+
+flat-cache@^1.2.1:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.2.2.tgz#fa86714e72c21db88601761ecf2f555d1abc6b96"
+ dependencies:
+ circular-json "^0.3.1"
+ del "^2.0.2"
+ graceful-fs "^4.1.2"
+ write "^0.2.1"
+
+for-in@^0.1.5:
+ version "0.1.6"
+ resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.6.tgz#c9f96e89bfad18a545af5ec3ed352a1d9e5b4dc8"
+
+for-own@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.4.tgz#0149b41a39088c7515f51ebe1c1386d45f935072"
+ dependencies:
+ for-in "^0.1.5"
+
+forever-agent@~0.6.1:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
+
+form-data@~2.1.1:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.2.tgz#89c3534008b97eada4cbb157d58f6f5df025eae4"
+ dependencies:
+ asynckit "^0.4.0"
+ combined-stream "^1.0.5"
+ mime-types "^2.1.12"
+
+forwarded@~0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.0.tgz#19ef9874c4ae1c297bcf078fde63a09b66a84363"
+
+fresh@0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.3.0.tgz#651f838e22424e7566de161d8358caa199f83d4f"
+
+fs-extra@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-1.0.0.tgz#cd3ce5f7e7cb6145883fcae3191e9877f8587950"
+ dependencies:
+ graceful-fs "^4.1.2"
+ jsonfile "^2.1.0"
+ klaw "^1.0.0"
+
+fs.realpath@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+
+fsevents@^1.0.0:
+ version "1.0.17"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.0.17.tgz#8537f3f12272678765b4fd6528c0f1f66f8f4558"
+ dependencies:
+ nan "^2.3.0"
+ node-pre-gyp "^0.6.29"
+
+fstream-ignore@~1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105"
+ dependencies:
+ fstream "^1.0.0"
+ inherits "2"
+ minimatch "^3.0.0"
+
+fstream@^1.0.0, fstream@^1.0.2, fstream@~1.0.10:
+ version "1.0.10"
+ resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.10.tgz#604e8a92fe26ffd9f6fae30399d4984e1ab22822"
+ dependencies:
+ graceful-fs "^4.1.2"
+ inherits "~2.0.0"
+ mkdirp ">=0.5 0"
+ rimraf "2"
+
+function-bind@^1.0.2:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771"
+
+gauge@~2.7.1:
+ version "2.7.2"
+ resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.2.tgz#15cecc31b02d05345a5d6b0e171cdb3ad2307774"
+ dependencies:
+ aproba "^1.0.3"
+ console-control-strings "^1.0.0"
+ has-unicode "^2.0.0"
+ object-assign "^4.1.0"
+ signal-exit "^3.0.0"
+ string-width "^1.0.1"
+ strip-ansi "^3.0.1"
+ supports-color "^0.2.0"
+ wide-align "^1.1.0"
+
+generate-function@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74"
+
+generate-object-property@^1.1.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0"
+ dependencies:
+ is-property "^1.0.0"
+
+get-caller-file@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5"
+
+getpass@^0.1.1:
+ version "0.1.6"
+ resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.6.tgz#283ffd9fc1256840875311c1b60e8c40187110e6"
+ dependencies:
+ assert-plus "^1.0.0"
+
+glob-base@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4"
+ dependencies:
+ glob-parent "^2.0.0"
+ is-glob "^2.0.0"
+
+glob-parent@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28"
+ dependencies:
+ is-glob "^2.0.0"
+
+glob@^5.0.15:
+ version "5.0.15"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1"
+ dependencies:
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "2 || 3"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1:
+ version "7.1.1"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.0.2"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+globals@^9.0.0, globals@^9.14.0:
+ version "9.14.0"
+ resolved "https://registry.yarnpkg.com/globals/-/globals-9.14.0.tgz#8859936af0038741263053b39d0e76ca241e4034"
+
+globby@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d"
+ dependencies:
+ array-union "^1.0.1"
+ arrify "^1.0.0"
+ glob "^7.0.3"
+ object-assign "^4.0.1"
+ pify "^2.0.0"
+ pinkie-promise "^2.0.0"
+
+graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9:
+ version "4.1.11"
+ resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
+
+"graceful-readlink@>= 1.0.0":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
+
+gzip-size@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-3.0.0.tgz#546188e9bdc337f673772f81660464b389dce520"
+ dependencies:
+ duplexer "^0.1.1"
+
+handle-thing@^1.2.4:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4"
+
+handlebars@^4.0.1, handlebars@^4.0.3:
+ version "4.0.6"
+ resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.6.tgz#2ce4484850537f9c97a8026d5399b935c4ed4ed7"
+ dependencies:
+ async "^1.4.0"
+ optimist "^0.6.1"
+ source-map "^0.4.4"
+ optionalDependencies:
+ uglify-js "^2.6"
+
+har-validator@~2.0.6:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d"
+ dependencies:
+ chalk "^1.1.1"
+ commander "^2.9.0"
+ is-my-json-valid "^2.12.4"
+ pinkie-promise "^2.0.0"
+
+has-ansi@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
+ dependencies:
+ ansi-regex "^2.0.0"
+
+has-binary@0.1.7:
+ version "0.1.7"
+ resolved "https://registry.yarnpkg.com/has-binary/-/has-binary-0.1.7.tgz#68e61eb16210c9545a0a5cce06a873912fe1e68c"
+ dependencies:
+ isarray "0.0.1"
+
+has-cors@1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39"
+
+has-flag@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa"
+
+has-unicode@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
+
+has@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28"
+ dependencies:
+ function-bind "^1.0.2"
+
+hash.js@^1.0.0:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.0.3.tgz#1332ff00156c0a0ffdd8236013d07b77a0451573"
+ dependencies:
+ inherits "^2.0.1"
+
+hasha@~2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/hasha/-/hasha-2.2.0.tgz#78d7cbfc1e6d66303fe79837365984517b2f6ee1"
+ dependencies:
+ is-stream "^1.0.1"
+ pinkie-promise "^2.0.0"
+
+hawk@~3.1.3:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4"
+ dependencies:
+ boom "2.x.x"
+ cryptiles "2.x.x"
+ hoek "2.x.x"
+ sntp "1.x.x"
+
+hoek@2.x.x:
+ version "2.16.3"
+ resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
+
+home-or-tmp@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
+ dependencies:
+ os-homedir "^1.0.0"
+ os-tmpdir "^1.0.1"
+
+hosted-git-info@^2.1.4:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.2.0.tgz#7a0d097863d886c0fabbdcd37bf1758d8becf8a5"
+
+hpack.js@^2.1.6:
+ version "2.1.6"
+ resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2"
+ dependencies:
+ inherits "^2.0.1"
+ obuf "^1.0.0"
+ readable-stream "^2.0.1"
+ wbuf "^1.1.0"
+
+html-entities@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.0.tgz#41948caf85ce82fed36e4e6a0ed371a6664379e2"
+
+http-deceiver@^1.2.4:
+ version "1.2.7"
+ resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
+
+http-errors@~1.5.0, http-errors@~1.5.1:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.5.1.tgz#788c0d2c1de2c81b9e6e8c01843b6b97eb920750"
+ dependencies:
+ inherits "2.0.3"
+ setprototypeof "1.0.2"
+ statuses ">= 1.3.1 < 2"
+
+http-proxy-middleware@~0.17.1:
+ version "0.17.3"
+ resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.17.3.tgz#940382147149b856084f5534752d5b5a8168cd1d"
+ dependencies:
+ http-proxy "^1.16.2"
+ is-glob "^3.1.0"
+ lodash "^4.17.2"
+ micromatch "^2.3.11"
+
+http-proxy@^1.13.0, http-proxy@^1.16.2:
+ version "1.16.2"
+ resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.16.2.tgz#06dff292952bf64dbe8471fa9df73066d4f37742"
+ dependencies:
+ eventemitter3 "1.x.x"
+ requires-port "1.x.x"
+
+http-signature@~1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf"
+ dependencies:
+ assert-plus "^0.2.0"
+ jsprim "^1.2.2"
+ sshpk "^1.7.0"
+
+https-browserify@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82"
+
+iconv-lite@0.4.15:
+ version "0.4.15"
+ resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb"
+
+ieee754@^1.1.4:
+ version "1.1.8"
+ resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"
+
+ignore@^3.2.0:
+ version "3.2.2"
+ resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.2.2.tgz#1c51e1ef53bab6ddc15db4d9ac4ec139eceb3410"
+
+imurmurhash@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
+
+indexof@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d"
+
+inflight@^1.0.4:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+ dependencies:
+ once "^1.3.0"
+ wrappy "1"
+
+inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
+
+inherits@2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
+
+ini@~1.3.0:
+ version "1.3.4"
+ resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e"
+
+inquirer@^0.12.0:
+ version "0.12.0"
+ resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e"
+ dependencies:
+ ansi-escapes "^1.1.0"
+ ansi-regex "^2.0.0"
+ chalk "^1.0.0"
+ cli-cursor "^1.0.1"
+ cli-width "^2.0.0"
+ figures "^1.3.5"
+ lodash "^4.3.0"
+ readline2 "^1.0.1"
+ run-async "^0.1.0"
+ rx-lite "^3.1.2"
+ string-width "^1.0.1"
+ strip-ansi "^3.0.0"
+ through "^2.3.6"
+
+interpret@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.1.tgz#d579fb7f693b858004947af39fa0db49f795602c"
+
+invariant@^2.2.0:
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360"
+ dependencies:
+ loose-envify "^1.0.0"
+
+invert-kv@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
+
+ipaddr.js@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.2.0.tgz#8aba49c9192799585bdd643e0ccb50e8ae777ba4"
+
+is-absolute@^0.2.3:
+ version "0.2.6"
+ resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-0.2.6.tgz#20de69f3db942ef2d87b9c2da36f172235b1b5eb"
+ dependencies:
+ is-relative "^0.2.1"
+ is-windows "^0.2.0"
+
+is-arrayish@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
+
+is-binary-path@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898"
+ dependencies:
+ binary-extensions "^1.0.0"
+
+is-buffer@^1.0.2:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.4.tgz#cfc86ccd5dc5a52fa80489111c6920c457e2d98b"
+
+is-builtin-module@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe"
+ dependencies:
+ builtin-modules "^1.0.0"
+
+is-dotfile@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.2.tgz#2c132383f39199f8edc268ca01b9b007d205cc4d"
+
+is-equal-shallow@^0.1.3:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534"
+ dependencies:
+ is-primitive "^2.0.0"
+
+is-extendable@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
+
+is-extglob@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0"
+
+is-extglob@^2.1.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
+
+is-finite@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa"
+ dependencies:
+ number-is-nan "^1.0.0"
+
+is-fullwidth-code-point@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
+ dependencies:
+ number-is-nan "^1.0.0"
+
+is-fullwidth-code-point@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
+
+is-glob@^2.0.0, is-glob@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863"
+ dependencies:
+ is-extglob "^1.0.0"
+
+is-glob@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a"
+ dependencies:
+ is-extglob "^2.1.0"
+
+is-my-json-valid@^2.10.0, is-my-json-valid@^2.12.4:
+ version "2.15.0"
+ resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.15.0.tgz#936edda3ca3c211fd98f3b2d3e08da43f7b2915b"
+ dependencies:
+ generate-function "^2.0.0"
+ generate-object-property "^1.1.0"
+ jsonpointer "^4.0.0"
+ xtend "^4.0.0"
+
+is-number@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/is-number/-/is-number-0.1.1.tgz#69a7af116963d47206ec9bd9b48a14216f1e3806"
+
+is-number@^2.0.2, is-number@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f"
+ dependencies:
+ kind-of "^3.0.2"
+
+is-path-cwd@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d"
+
+is-path-in-cwd@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc"
+ dependencies:
+ is-path-inside "^1.0.0"
+
+is-path-inside@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.0.tgz#fc06e5a1683fbda13de667aff717bbc10a48f37f"
+ dependencies:
+ path-is-inside "^1.0.1"
+
+is-posix-bracket@^0.1.0:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4"
+
+is-primitive@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575"
+
+is-property@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84"
+
+is-relative@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-0.2.1.tgz#d27f4c7d516d175fb610db84bbeef23c3bc97aa5"
+ dependencies:
+ is-unc-path "^0.1.1"
+
+is-resolvable@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.0.0.tgz#8df57c61ea2e3c501408d100fb013cf8d6e0cc62"
+ dependencies:
+ tryit "^1.0.1"
+
+is-stream@^1.0.1:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
+
+is-typedarray@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
+
+is-unc-path@^0.1.1:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-0.1.2.tgz#6ab053a72573c10250ff416a3814c35178af39b9"
+ dependencies:
+ unc-path-regex "^0.1.0"
+
+is-utf8@^0.2.0:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
+
+is-windows@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-0.2.0.tgz#de1aa6d63ea29dd248737b69f1ff8b8002d2108c"
+
+isarray@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
+
+isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
+
+isbinaryfile@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-3.0.2.tgz#4a3e974ec0cba9004d3fc6cde7209ea69368a621"
+
+isexe@^1.1.1:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/isexe/-/isexe-1.1.2.tgz#36f3e22e60750920f5e7241a476a8c6a42275ad0"
+
+isobject@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"
+ dependencies:
+ isarray "1.0.0"
+
+isstream@~0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
+
+istanbul-api@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.1.1.tgz#d36e2f1560d1a43ce304c4ff7338182de61c8f73"
+ dependencies:
+ async "^2.1.4"
+ fileset "^2.0.2"
+ istanbul-lib-coverage "^1.0.0"
+ istanbul-lib-hook "^1.0.0"
+ istanbul-lib-instrument "^1.3.0"
+ istanbul-lib-report "^1.0.0-alpha.3"
+ istanbul-lib-source-maps "^1.1.0"
+ istanbul-reports "^1.0.0"
+ js-yaml "^3.7.0"
+ mkdirp "^0.5.1"
+ once "^1.4.0"
+
+istanbul-lib-coverage@^1.0.0, istanbul-lib-coverage@^1.0.0-alpha, istanbul-lib-coverage@^1.0.0-alpha.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.0.1.tgz#f263efb519c051c5f1f3343034fc40e7b43ff212"
+
+istanbul-lib-hook@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.0.0.tgz#fc5367ee27f59268e8f060b0c7aaf051d9c425c5"
+ dependencies:
+ append-transform "^0.4.0"
+
+istanbul-lib-instrument@^1.3.0, istanbul-lib-instrument@^1.4.2:
+ version "1.4.2"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.4.2.tgz#0e2fdfac93c1dabf2e31578637dc78a19089f43e"
+ dependencies:
+ babel-generator "^6.18.0"
+ babel-template "^6.16.0"
+ babel-traverse "^6.18.0"
+ babel-types "^6.18.0"
+ babylon "^6.13.0"
+ istanbul-lib-coverage "^1.0.0"
+ semver "^5.3.0"
+
+istanbul-lib-report@^1.0.0-alpha.3:
+ version "1.0.0-alpha.3"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.0.0-alpha.3.tgz#32d5f6ec7f33ca3a602209e278b2e6ff143498af"
+ dependencies:
+ async "^1.4.2"
+ istanbul-lib-coverage "^1.0.0-alpha"
+ mkdirp "^0.5.1"
+ path-parse "^1.0.5"
+ rimraf "^2.4.3"
+ supports-color "^3.1.2"
+
+istanbul-lib-source-maps@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.1.0.tgz#9d429218f35b823560ea300a96ff0c3bbdab785f"
+ dependencies:
+ istanbul-lib-coverage "^1.0.0-alpha.0"
+ mkdirp "^0.5.1"
+ rimraf "^2.4.4"
+ source-map "^0.5.3"
+
+istanbul-reports@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.0.1.tgz#9a17176bc4a6cbebdae52b2f15961d52fa623fbc"
+ dependencies:
+ handlebars "^4.0.3"
+
+istanbul@^0.4.5:
+ version "0.4.5"
+ resolved "https://registry.yarnpkg.com/istanbul/-/istanbul-0.4.5.tgz#65c7d73d4c4da84d4f3ac310b918fb0b8033733b"
+ dependencies:
+ abbrev "1.0.x"
+ async "1.x"
+ escodegen "1.8.x"
+ esprima "2.7.x"
+ glob "^5.0.15"
+ handlebars "^4.0.1"
+ js-yaml "3.x"
+ mkdirp "0.5.x"
+ nopt "3.x"
+ once "1.x"
+ resolve "1.1.x"
+ supports-color "^3.1.0"
+ which "^1.1.1"
+ wordwrap "^1.0.0"
+
+jasmine-core@^2.5.2:
+ version "2.5.2"
+ resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.5.2.tgz#6f61bd79061e27f43e6f9355e44b3c6cab6ff297"
+
+jasmine-jquery@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/jasmine-jquery/-/jasmine-jquery-2.1.1.tgz#d4095e646944a26763235769ab018d9f30f0d47b"
+
+jodid25519@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/jodid25519/-/jodid25519-1.0.2.tgz#06d4912255093419477d425633606e0e90782967"
+ dependencies:
+ jsbn "~0.1.0"
+
+"jquery-ui@git+https://github.com/jquery/jquery-ui#1.11.4":
+ version "1.11.4"
+ resolved "git+https://github.com/jquery/jquery-ui#d6713024e16de90ea71dc0544ba34e1df01b4d8a"
+
+jquery-ujs@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/jquery-ujs/-/jquery-ujs-1.2.1.tgz#6ee75b1ef4e9ac95e7124f8d71f7d351f5548e92"
+ dependencies:
+ jquery ">=1.8.0"
+
+jquery@>=1.8.0, jquery@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/jquery/-/jquery-2.2.1.tgz#3c3e16854ad3d2ac44ac65021b17426d22ad803f"
+
+js-cookie@^2.1.3:
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.1.3.tgz#48071625217ac9ecfab8c343a13d42ec09ff0526"
+
+js-tokens@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7"
+
+js-yaml@3.x, js-yaml@^3.5.1, js-yaml@^3.7.0:
+ version "3.8.1"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.8.1.tgz#782ba50200be7b9e5a8537001b7804db3ad02628"
+ dependencies:
+ argparse "^1.0.7"
+ esprima "^3.1.1"
+
+jsbn@~0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.0.tgz#650987da0dd74f4ebf5a11377a2aa2d273e97dfd"
+
+jsesc@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b"
+
+jsesc@~0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
+
+json-loader@^0.5.4:
+ version "0.5.4"
+ resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.4.tgz#8baa1365a632f58a3c46d20175fc6002c96e37de"
+
+json-schema@0.2.3:
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
+
+json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af"
+ dependencies:
+ jsonify "~0.0.0"
+
+json-stringify-safe@~5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
+
+json3@3.3.2, json3@^3.3.2:
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1"
+
+json5@^0.5.0:
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
+
+jsonfile@^2.1.0:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8"
+ optionalDependencies:
+ graceful-fs "^4.1.6"
+
+jsonify@~0.0.0:
+ version "0.0.0"
+ resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
+
+jsonpointer@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9"
+
+jsprim@^1.2.2:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.3.1.tgz#2a7256f70412a29ee3670aaca625994c4dcff252"
+ dependencies:
+ extsprintf "1.0.2"
+ json-schema "0.2.3"
+ verror "1.3.6"
+
+karma-coverage-istanbul-reporter@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-0.2.0.tgz#5766263338adeb0026f7e4ac7a89a5f056c5642c"
+ dependencies:
+ istanbul-api "^1.1.1"
+
+karma-jasmine@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/karma-jasmine/-/karma-jasmine-1.1.0.tgz#22e4c06bf9a182e5294d1f705e3733811b810acf"
+
+karma-mocha-reporter@^2.2.2:
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/karma-mocha-reporter/-/karma-mocha-reporter-2.2.2.tgz#876de9a287244e54a608591732a98e66611f6abe"
+ dependencies:
+ chalk "1.1.3"
+
+karma-phantomjs-launcher@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/karma-phantomjs-launcher/-/karma-phantomjs-launcher-1.0.2.tgz#19e1041498fd75563ed86730a22c1fe579fa8fb1"
+ dependencies:
+ lodash "^4.0.1"
+ phantomjs-prebuilt "^2.1.7"
+
+karma-sourcemap-loader@^0.3.7:
+ version "0.3.7"
+ resolved "https://registry.yarnpkg.com/karma-sourcemap-loader/-/karma-sourcemap-loader-0.3.7.tgz#91322c77f8f13d46fed062b042e1009d4c4505d8"
+ dependencies:
+ graceful-fs "^4.1.2"
+
+karma-webpack@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-2.0.2.tgz#bd38350af5645c9644090770939ebe7ce726f864"
+ dependencies:
+ async "~0.9.0"
+ loader-utils "^0.2.5"
+ lodash "^3.8.0"
+ source-map "^0.1.41"
+ webpack-dev-middleware "^1.0.11"
+
+karma@^1.4.1:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/karma/-/karma-1.4.1.tgz#41981a71d54237606b0a3ea8c58c90773f41650e"
+ dependencies:
+ bluebird "^3.3.0"
+ body-parser "^1.12.4"
+ chokidar "^1.4.1"
+ colors "^1.1.0"
+ combine-lists "^1.0.0"
+ connect "^3.3.5"
+ core-js "^2.2.0"
+ di "^0.0.1"
+ dom-serialize "^2.2.0"
+ expand-braces "^0.1.1"
+ glob "^7.1.1"
+ graceful-fs "^4.1.2"
+ http-proxy "^1.13.0"
+ isbinaryfile "^3.0.0"
+ lodash "^3.8.0"
+ log4js "^0.6.31"
+ mime "^1.3.4"
+ minimatch "^3.0.0"
+ optimist "^0.6.1"
+ qjobs "^1.1.4"
+ range-parser "^1.2.0"
+ rimraf "^2.3.3"
+ safe-buffer "^5.0.1"
+ socket.io "1.7.2"
+ source-map "^0.5.3"
+ tmp "0.0.28"
+ useragent "^2.1.10"
+
+kew@~0.7.0:
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/kew/-/kew-0.7.0.tgz#79d93d2d33363d6fdd2970b335d9141ad591d79b"
+
+kind-of@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.1.0.tgz#475d698a5e49ff5e53d14e3e732429dc8bf4cf47"
+ dependencies:
+ is-buffer "^1.0.2"
+
+klaw@^1.0.0:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439"
+ optionalDependencies:
+ graceful-fs "^4.1.9"
+
+lazy-cache@^1.0.3:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e"
+
+lcid@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
+ dependencies:
+ invert-kv "^1.0.0"
+
+levn@^0.3.0, levn@~0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
+ dependencies:
+ prelude-ls "~1.1.2"
+ type-check "~0.3.2"
+
+load-json-file@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
+ dependencies:
+ graceful-fs "^4.1.2"
+ parse-json "^2.2.0"
+ pify "^2.0.0"
+ pinkie-promise "^2.0.0"
+ strip-bom "^2.0.0"
+
+loader-runner@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2"
+
+loader-utils@^0.2.11, loader-utils@^0.2.16, loader-utils@^0.2.5:
+ version "0.2.16"
+ resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.16.tgz#f08632066ed8282835dff88dfb52704765adee6d"
+ dependencies:
+ big.js "^3.1.3"
+ emojis-list "^2.0.0"
+ json5 "^0.5.0"
+ object-assign "^4.0.1"
+
+locate-path@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
+ dependencies:
+ p-locate "^2.0.0"
+ path-exists "^3.0.0"
+
+lodash._baseget@^3.0.0:
+ version "3.7.2"
+ resolved "https://registry.yarnpkg.com/lodash._baseget/-/lodash._baseget-3.7.2.tgz#1b6ae1d5facf3c25532350a13c1197cb8bb674f4"
+
+lodash._topath@^3.0.0:
+ version "3.8.1"
+ resolved "https://registry.yarnpkg.com/lodash._topath/-/lodash._topath-3.8.1.tgz#3ec5e2606014f4cb97f755fe6914edd8bfc00eac"
+ dependencies:
+ lodash.isarray "^3.0.0"
+
+lodash.camelcase@4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.1.1.tgz#065b3ff08f0b7662f389934c46a5504c90e0b2d8"
+ dependencies:
+ lodash.capitalize "^4.0.0"
+ lodash.deburr "^4.0.0"
+ lodash.words "^4.0.0"
+
+lodash.capitalize@^4.0.0:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz#f826c9b4e2a8511d84e3aca29db05e1a4f3b72a9"
+
+lodash.cond@^4.3.0:
+ version "4.5.2"
+ resolved "https://registry.yarnpkg.com/lodash.cond/-/lodash.cond-4.5.2.tgz#f471a1da486be60f6ab955d17115523dd1d255d5"
+
+lodash.deburr@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/lodash.deburr/-/lodash.deburr-4.1.0.tgz#ddb1bbb3ef07458c0177ba07de14422cb033ff9b"
+
+lodash.get@^3.7.0:
+ version "3.7.0"
+ resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-3.7.0.tgz#3ce68ae2c91683b281cc5394128303cbf75e691f"
+ dependencies:
+ lodash._baseget "^3.0.0"
+ lodash._topath "^3.0.0"
+
+lodash.isarray@^3.0.0:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55"
+
+lodash.kebabcase@4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.0.1.tgz#5e63bc9aa2a5562ff3b97ca7af2f803de1bcb90e"
+ dependencies:
+ lodash.deburr "^4.0.0"
+ lodash.words "^4.0.0"
+
+lodash.snakecase@4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.0.1.tgz#bd012e5d2f93f7b58b9303e9a7fbfd5db13d6281"
+ dependencies:
+ lodash.deburr "^4.0.0"
+ lodash.words "^4.0.0"
+
+lodash.words@^4.0.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/lodash.words/-/lodash.words-4.2.0.tgz#5ecfeaf8ecf8acaa8e0c8386295f1993c9cf4036"
+
+lodash@^3.8.0:
+ version "3.10.1"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
+
+lodash@^4.0.0, lodash@^4.0.1, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@^4.5.0:
+ version "4.17.4"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
+
+log4js@^0.6.31:
+ version "0.6.38"
+ resolved "https://registry.yarnpkg.com/log4js/-/log4js-0.6.38.tgz#2c494116695d6fb25480943d3fc872e662a522fd"
+ dependencies:
+ readable-stream "~1.0.2"
+ semver "~4.3.3"
+
+longest@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
+
+loose-envify@^1.0.0:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
+ dependencies:
+ js-tokens "^3.0.0"
+
+lru-cache@2.2.x:
+ version "2.2.4"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.2.4.tgz#6c658619becf14031d0d0b594b16042ce4dc063d"
+
+media-typer@0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
+
+memory-fs@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.2.0.tgz#f2bb25368bc121e391c2520de92969caee0a0290"
+
+memory-fs@^0.4.0, memory-fs@~0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552"
+ dependencies:
+ errno "^0.1.3"
+ readable-stream "^2.0.1"
+
+merge-descriptors@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
+
+methods@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
+
+micromatch@^2.1.5, micromatch@^2.3.11:
+ version "2.3.11"
+ resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
+ dependencies:
+ arr-diff "^2.0.0"
+ array-unique "^0.2.1"
+ braces "^1.8.2"
+ expand-brackets "^0.1.4"
+ extglob "^0.3.1"
+ filename-regex "^2.0.0"
+ is-extglob "^1.0.0"
+ is-glob "^2.0.1"
+ kind-of "^3.0.2"
+ normalize-path "^2.0.1"
+ object.omit "^2.0.0"
+ parse-glob "^3.0.4"
+ regex-cache "^0.4.2"
+
+miller-rabin@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.0.tgz#4a62fb1d42933c05583982f4c716f6fb9e6c6d3d"
+ dependencies:
+ bn.js "^4.0.0"
+ brorand "^1.0.1"
+
+"mime-db@>= 1.24.0 < 2", mime-db@~1.26.0:
+ version "1.26.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.26.0.tgz#eaffcd0e4fc6935cf8134da246e2e6c35305adff"
+
+mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.13, mime-types@~2.1.7:
+ version "2.1.14"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.14.tgz#f7ef7d97583fcaf3b7d282b6f8b5679dab1e94ee"
+ dependencies:
+ mime-db "~1.26.0"
+
+mime@1.3.4, mime@^1.3.4:
+ version "1.3.4"
+ resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53"
+
+minimalistic-assert@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3"
+
+"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774"
+ dependencies:
+ brace-expansion "^1.0.0"
+
+minimist@0.0.8, minimist@~0.0.1:
+ version "0.0.8"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
+
+minimist@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
+
+mkdirp@0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.0.tgz#1d73076a6df986cd9344e15e71fcc05a4c9abf12"
+ dependencies:
+ minimist "0.0.8"
+
+mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
+ dependencies:
+ minimist "0.0.8"
+
+moment@2.x:
+ version "2.17.1"
+ resolved "https://registry.yarnpkg.com/moment/-/moment-2.17.1.tgz#fed9506063f36b10f066c8b59a144d7faebe1d82"
+
+mousetrap@^1.4.6:
+ version "1.4.6"
+ resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.4.6.tgz#eaca72e22e56d5b769b7555873b688c3332e390a"
+
+ms@0.7.1:
+ version "0.7.1"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098"
+
+ms@0.7.2:
+ version "0.7.2"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765"
+
+mute-stream@0.0.5:
+ version "0.0.5"
+ resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0"
+
+nan@^2.0.0, nan@^2.3.0:
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/nan/-/nan-2.5.1.tgz#d5b01691253326a97a2bbee9e61c55d8d60351e2"
+
+natural-compare@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
+
+negotiator@0.6.1:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
+
+node-libs-browser@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-1.1.1.tgz#2a38243abedd7dffcd07a97c9aca5668975a6fea"
+ dependencies:
+ assert "^1.1.1"
+ browserify-zlib "^0.1.4"
+ buffer "^4.3.0"
+ console-browserify "^1.1.0"
+ constants-browserify "^1.0.0"
+ crypto-browserify "^3.11.0"
+ domain-browser "^1.1.1"
+ events "^1.0.0"
+ https-browserify "0.0.1"
+ os-browserify "^0.2.0"
+ path-browserify "0.0.0"
+ process "^0.11.0"
+ punycode "^1.2.4"
+ querystring-es3 "^0.2.0"
+ readable-stream "^2.0.5"
+ stream-browserify "^2.0.1"
+ stream-http "^2.3.1"
+ string_decoder "^0.10.25"
+ timers-browserify "^1.4.2"
+ tty-browserify "0.0.0"
+ url "^0.11.0"
+ util "^0.10.3"
+ vm-browserify "0.0.4"
+
+node-libs-browser@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.0.0.tgz#a3a59ec97024985b46e958379646f96c4b616646"
+ dependencies:
+ assert "^1.1.1"
+ browserify-zlib "^0.1.4"
+ buffer "^4.3.0"
+ console-browserify "^1.1.0"
+ constants-browserify "^1.0.0"
+ crypto-browserify "^3.11.0"
+ domain-browser "^1.1.1"
+ events "^1.0.0"
+ https-browserify "0.0.1"
+ os-browserify "^0.2.0"
+ path-browserify "0.0.0"
+ process "^0.11.0"
+ punycode "^1.2.4"
+ querystring-es3 "^0.2.0"
+ readable-stream "^2.0.5"
+ stream-browserify "^2.0.1"
+ stream-http "^2.3.1"
+ string_decoder "^0.10.25"
+ timers-browserify "^2.0.2"
+ tty-browserify "0.0.0"
+ url "^0.11.0"
+ util "^0.10.3"
+ vm-browserify "0.0.4"
+
+node-pre-gyp@^0.6.29, node-pre-gyp@^0.6.4:
+ version "0.6.33"
+ resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.33.tgz#640ac55198f6a925972e0c16c4ac26a034d5ecc9"
+ dependencies:
+ mkdirp "~0.5.1"
+ nopt "~3.0.6"
+ npmlog "^4.0.1"
+ rc "~1.1.6"
+ request "^2.79.0"
+ rimraf "~2.5.4"
+ semver "~5.3.0"
+ tar "~2.2.1"
+ tar-pack "~3.3.0"
+
+node-zopfli@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/node-zopfli/-/node-zopfli-2.0.2.tgz#a7a473ae92aaea85d4c68d45bbf2c944c46116b8"
+ dependencies:
+ commander "^2.8.1"
+ defaults "^1.0.2"
+ nan "^2.0.0"
+ node-pre-gyp "^0.6.4"
+
+nopt@3.x, nopt@~3.0.6:
+ version "3.0.6"
+ resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
+ dependencies:
+ abbrev "1"
+
+normalize-package-data@^2.3.2:
+ version "2.3.5"
+ resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.3.5.tgz#8d924f142960e1777e7ffe170543631cc7cb02df"
+ dependencies:
+ hosted-git-info "^2.1.4"
+ is-builtin-module "^1.0.0"
+ semver "2 || 3 || 4 || 5"
+ validate-npm-package-license "^3.0.1"
+
+normalize-path@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.0.1.tgz#47886ac1662760d4261b7d979d241709d3ce3f7a"
+
+npmlog@^4.0.1:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.0.2.tgz#d03950e0e78ce1527ba26d2a7592e9348ac3e75f"
+ dependencies:
+ are-we-there-yet "~1.1.2"
+ console-control-strings "~1.1.0"
+ gauge "~2.7.1"
+ set-blocking "~2.0.0"
+
+number-is-nan@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
+
+oauth-sign@~0.8.1:
+ version "0.8.2"
+ resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
+
+object-assign@4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0"
+
+object-assign@^4.0.1, object-assign@^4.1.0:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
+
+object-component@0.0.3:
+ version "0.0.3"
+ resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291"
+
+object.omit@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa"
+ dependencies:
+ for-own "^0.1.4"
+ is-extendable "^0.1.1"
+
+obuf@^1.0.0, obuf@^1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.1.tgz#104124b6c602c6796881a042541d36db43a5264e"
+
+on-finished@~2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
+ dependencies:
+ ee-first "1.1.1"
+
+on-headers@~1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7"
+
+once@1.x, once@^1.3.0, once@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+ dependencies:
+ wrappy "1"
+
+once@~1.3.3:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/once/-/once-1.3.3.tgz#b2e261557ce4c314ec8304f3fa82663e4297ca20"
+ dependencies:
+ wrappy "1"
+
+onetime@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789"
+
+opener@^1.4.2:
+ version "1.4.3"
+ resolved "https://registry.yarnpkg.com/opener/-/opener-1.4.3.tgz#5c6da2c5d7e5831e8ffa3964950f8d6674ac90b8"
+
+opn@4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/opn/-/opn-4.0.2.tgz#7abc22e644dff63b0a96d5ab7f2790c0f01abc95"
+ dependencies:
+ object-assign "^4.0.1"
+ pinkie-promise "^2.0.0"
+
+optimist@^0.6.1:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
+ dependencies:
+ minimist "~0.0.1"
+ wordwrap "~0.0.2"
+
+optionator@^0.8.1, optionator@^0.8.2:
+ version "0.8.2"
+ resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64"
+ dependencies:
+ deep-is "~0.1.3"
+ fast-levenshtein "~2.0.4"
+ levn "~0.3.0"
+ prelude-ls "~1.1.2"
+ type-check "~0.3.2"
+ wordwrap "~1.0.0"
+
+options@>=0.0.5:
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f"
+
+original@>=0.0.5:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/original/-/original-1.0.0.tgz#9147f93fa1696d04be61e01bd50baeaca656bd3b"
+ dependencies:
+ url-parse "1.0.x"
+
+os-browserify@^0.2.0:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.2.1.tgz#63fc4ccee5d2d7763d26bbf8601078e6c2e0044f"
+
+os-homedir@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
+
+os-locale@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9"
+ dependencies:
+ lcid "^1.0.0"
+
+os-tmpdir@^1.0.1, os-tmpdir@~1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
+
+p-limit@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc"
+
+p-locate@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
+ dependencies:
+ p-limit "^1.1.0"
+
+pako@~0.2.0:
+ version "0.2.9"
+ resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75"
+
+parse-asn1@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.0.0.tgz#35060f6d5015d37628c770f4e091a0b5a278bc23"
+ dependencies:
+ asn1.js "^4.0.0"
+ browserify-aes "^1.0.0"
+ create-hash "^1.1.0"
+ evp_bytestokey "^1.0.0"
+ pbkdf2 "^3.0.3"
+
+parse-glob@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c"
+ dependencies:
+ glob-base "^0.3.0"
+ is-dotfile "^1.0.0"
+ is-extglob "^1.0.0"
+ is-glob "^2.0.0"
+
+parse-json@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9"
+ dependencies:
+ error-ex "^1.2.0"
+
+parsejson@0.0.3:
+ version "0.0.3"
+ resolved "https://registry.yarnpkg.com/parsejson/-/parsejson-0.0.3.tgz#ab7e3759f209ece99437973f7d0f1f64ae0e64ab"
+ dependencies:
+ better-assert "~1.0.0"
+
+parseqs@0.0.5:
+ version "0.0.5"
+ resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d"
+ dependencies:
+ better-assert "~1.0.0"
+
+parseuri@0.0.5:
+ version "0.0.5"
+ resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a"
+ dependencies:
+ better-assert "~1.0.0"
+
+parseurl@~1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56"
+
+path-browserify@0.0.0:
+ version "0.0.0"
+ resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a"
+
+path-exists@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b"
+ dependencies:
+ pinkie-promise "^2.0.0"
+
+path-exists@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
+
+path-is-absolute@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+
+path-is-inside@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
+
+path-parse@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1"
+
+path-to-regexp@0.1.7:
+ version "0.1.7"
+ resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
+
+path-type@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
+ dependencies:
+ graceful-fs "^4.1.2"
+ pify "^2.0.0"
+ pinkie-promise "^2.0.0"
+
+pbkdf2@^3.0.3:
+ version "3.0.9"
+ resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.9.tgz#f2c4b25a600058b3c3773c086c37dbbee1ffe693"
+ dependencies:
+ create-hmac "^1.1.2"
+
+pend@~1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
+
+phantomjs-prebuilt@^2.1.7:
+ version "2.1.14"
+ resolved "https://registry.yarnpkg.com/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.14.tgz#d53d311fcfb7d1d08ddb24014558f1188c516da0"
+ dependencies:
+ es6-promise "~4.0.3"
+ extract-zip "~1.5.0"
+ fs-extra "~1.0.0"
+ hasha "~2.2.0"
+ kew "~0.7.0"
+ progress "~1.1.8"
+ request "~2.79.0"
+ request-progress "~2.0.1"
+ which "~1.2.10"
+
+pify@^2.0.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
+
+pikaday@^1.5.1:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/pikaday/-/pikaday-1.5.1.tgz#0a48549bc1a14ea1d08c44074d761bc2f2bfcfd3"
+ optionalDependencies:
+ moment "2.x"
+
+pinkie-promise@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
+ dependencies:
+ pinkie "^2.0.0"
+
+pinkie@^2.0.0:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
+
+pkg-dir@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4"
+ dependencies:
+ find-up "^1.0.0"
+
+pkg-up@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-1.0.0.tgz#3e08fb461525c4421624a33b9f7e6d0af5b05a26"
+ dependencies:
+ find-up "^1.0.0"
+
+pluralize@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45"
+
+portfinder@^1.0.9:
+ version "1.0.13"
+ resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9"
+ dependencies:
+ async "^1.5.2"
+ debug "^2.2.0"
+ mkdirp "0.5.x"
+
+prelude-ls@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
+
+preserve@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
+
+private@^0.1.6:
+ version "0.1.7"
+ resolved "https://registry.yarnpkg.com/private/-/private-0.1.7.tgz#68ce5e8a1ef0a23bb570cc28537b5332aba63ef1"
+
+process-nextick-args@~1.0.6:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3"
+
+process@^0.11.0, process@~0.11.0:
+ version "0.11.9"
+ resolved "https://registry.yarnpkg.com/process/-/process-0.11.9.tgz#7bd5ad21aa6253e7da8682264f1e11d11c0318c1"
+
+progress@^1.1.8, progress@~1.1.8:
+ version "1.1.8"
+ resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be"
+
+proxy-addr@~1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.3.tgz#dc97502f5722e888467b3fa2297a7b1ff47df074"
+ dependencies:
+ forwarded "~0.1.0"
+ ipaddr.js "1.2.0"
+
+prr@~0.0.0:
+ version "0.0.0"
+ resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a"
+
+public-encrypt@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.0.tgz#39f699f3a46560dd5ebacbca693caf7c65c18cc6"
+ dependencies:
+ bn.js "^4.1.0"
+ browserify-rsa "^4.0.0"
+ create-hash "^1.1.0"
+ parse-asn1 "^5.0.0"
+ randombytes "^2.0.1"
+
+punycode@1.3.2:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
+
+punycode@^1.2.4, punycode@^1.4.1:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
+
+qjobs@^1.1.4:
+ version "1.1.5"
+ resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.1.5.tgz#659de9f2cf8dcc27a1481276f205377272382e73"
+
+qs@6.2.0:
+ version "6.2.0"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.0.tgz#3b7848c03c2dece69a9522b0fae8c4126d745f3b"
+
+qs@6.2.1:
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.1.tgz#ce03c5ff0935bc1d9d69a9f14cbd18e568d67625"
+
+qs@~6.3.0:
+ version "6.3.0"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.0.tgz#f403b264f23bc01228c74131b407f18d5ea5d442"
+
+querystring-es3@^0.2.0:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
+
+querystring@0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
+
+querystringify@0.0.x:
+ version "0.0.4"
+ resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-0.0.4.tgz#0cf7f84f9463ff0ae51c4c4b142d95be37724d9c"
+
+randomatic@^1.1.3:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.6.tgz#110dcabff397e9dcff7c0789ccc0a49adf1ec5bb"
+ dependencies:
+ is-number "^2.0.2"
+ kind-of "^3.0.2"
+
+randombytes@^2.0.0, randombytes@^2.0.1:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.3.tgz#674c99760901c3c4112771a31e521dc349cc09ec"
+
+range-parser@^1.0.3, range-parser@^1.2.0, range-parser@~1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
+
+raw-body@~2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.2.0.tgz#994976cf6a5096a41162840492f0bdc5d6e7fb96"
+ dependencies:
+ bytes "2.4.0"
+ iconv-lite "0.4.15"
+ unpipe "1.0.0"
+
+rc@~1.1.6:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/rc/-/rc-1.1.6.tgz#43651b76b6ae53b5c802f1151fa3fc3b059969c9"
+ dependencies:
+ deep-extend "~0.4.0"
+ ini "~1.3.0"
+ minimist "^1.2.0"
+ strip-json-comments "~1.0.4"
+
+read-pkg-up@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
+ dependencies:
+ find-up "^1.0.0"
+ read-pkg "^1.0.0"
+
+read-pkg@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28"
+ dependencies:
+ load-json-file "^1.0.0"
+ normalize-package-data "^2.3.2"
+ path-type "^1.0.0"
+
+"readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.1.0, readable-stream@^2.2.2:
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.2.tgz#a9e6fec3c7dda85f8bb1b3ba7028604556fc825e"
+ dependencies:
+ buffer-shims "^1.0.0"
+ core-util-is "~1.0.0"
+ inherits "~2.0.1"
+ isarray "~1.0.0"
+ process-nextick-args "~1.0.6"
+ string_decoder "~0.10.x"
+ util-deprecate "~1.0.1"
+
+readable-stream@~1.0.2:
+ version "1.0.34"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
+ dependencies:
+ core-util-is "~1.0.0"
+ inherits "~2.0.1"
+ isarray "0.0.1"
+ string_decoder "~0.10.x"
+
+readable-stream@~2.0.0:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e"
+ dependencies:
+ core-util-is "~1.0.0"
+ inherits "~2.0.1"
+ isarray "~1.0.0"
+ process-nextick-args "~1.0.6"
+ string_decoder "~0.10.x"
+ util-deprecate "~1.0.1"
+
+readable-stream@~2.1.4:
+ version "2.1.5"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.1.5.tgz#66fa8b720e1438b364681f2ad1a63c618448c9d0"
+ dependencies:
+ buffer-shims "^1.0.0"
+ core-util-is "~1.0.0"
+ inherits "~2.0.1"
+ isarray "~1.0.0"
+ process-nextick-args "~1.0.6"
+ string_decoder "~0.10.x"
+ util-deprecate "~1.0.1"
+
+readdirp@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78"
+ dependencies:
+ graceful-fs "^4.1.2"
+ minimatch "^3.0.2"
+ readable-stream "^2.0.2"
+ set-immediate-shim "^1.0.1"
+
+readline2@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/readline2/-/readline2-1.0.1.tgz#41059608ffc154757b715d9989d199ffbf372e35"
+ dependencies:
+ code-point-at "^1.0.0"
+ is-fullwidth-code-point "^1.0.0"
+ mute-stream "0.0.5"
+
+rechoir@^0.6.2:
+ version "0.6.2"
+ resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"
+ dependencies:
+ resolve "^1.1.6"
+
+regenerate@^1.2.1:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.2.tgz#d1941c67bad437e1be76433add5b385f95b19260"
+
+regenerator-runtime@^0.10.0:
+ version "0.10.1"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.1.tgz#257f41961ce44558b18f7814af48c17559f9faeb"
+
+regenerator-transform@0.9.8:
+ version "0.9.8"
+ resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.9.8.tgz#0f88bb2bc03932ddb7b6b7312e68078f01026d6c"
+ dependencies:
+ babel-runtime "^6.18.0"
+ babel-types "^6.19.0"
+ private "^0.1.6"
+
+regex-cache@^0.4.2:
+ version "0.4.3"
+ resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.3.tgz#9b1a6c35d4d0dfcef5711ae651e8e9d3d7114145"
+ dependencies:
+ is-equal-shallow "^0.1.3"
+ is-primitive "^2.0.0"
+
+regexpu-core@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240"
+ dependencies:
+ regenerate "^1.2.1"
+ regjsgen "^0.2.0"
+ regjsparser "^0.1.4"
+
+regjsgen@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7"
+
+regjsparser@^0.1.4:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c"
+ dependencies:
+ jsesc "~0.5.0"
+
+repeat-element@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a"
+
+repeat-string@^0.2.2:
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-0.2.2.tgz#c7a8d3236068362059a7e4651fc6884e8b1fb4ae"
+
+repeat-string@^1.5.2:
+ version "1.6.1"
+ resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
+
+repeating@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda"
+ dependencies:
+ is-finite "^1.0.0"
+
+request-progress@~2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-2.0.1.tgz#5d36bb57961c673aa5b788dbc8141fdf23b44e08"
+ dependencies:
+ throttleit "^1.0.0"
+
+request@^2.79.0, request@~2.79.0:
+ version "2.79.0"
+ resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de"
+ dependencies:
+ aws-sign2 "~0.6.0"
+ aws4 "^1.2.1"
+ caseless "~0.11.0"
+ combined-stream "~1.0.5"
+ extend "~3.0.0"
+ forever-agent "~0.6.1"
+ form-data "~2.1.1"
+ har-validator "~2.0.6"
+ hawk "~3.1.3"
+ http-signature "~1.1.0"
+ is-typedarray "~1.0.0"
+ isstream "~0.1.2"
+ json-stringify-safe "~5.0.1"
+ mime-types "~2.1.7"
+ oauth-sign "~0.8.1"
+ qs "~6.3.0"
+ stringstream "~0.0.4"
+ tough-cookie "~2.3.0"
+ tunnel-agent "~0.4.1"
+ uuid "^3.0.0"
+
+require-directory@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
+
+require-main-filename@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
+
+require-uncached@^1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3"
+ dependencies:
+ caller-path "^0.1.0"
+ resolve-from "^1.0.0"
+
+requires-port@1.0.x, requires-port@1.x.x:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
+
+resolve-from@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226"
+
+resolve@1.1.x:
+ version "1.1.7"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
+
+resolve@^1.1.6, resolve@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.2.0.tgz#9589c3f2f6149d1417a40becc1663db6ec6bc26c"
+
+restore-cursor@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541"
+ dependencies:
+ exit-hook "^1.0.0"
+ onetime "^1.0.0"
+
+right-align@^0.1.1:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef"
+ dependencies:
+ align-text "^0.1.1"
+
+rimraf@2, rimraf@^2.2.8, rimraf@^2.3.3, rimraf@^2.4.3, rimraf@^2.4.4, rimraf@~2.5.1, rimraf@~2.5.4:
+ version "2.5.4"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.5.4.tgz#96800093cbf1a0c86bd95b4625467535c29dfa04"
+ dependencies:
+ glob "^7.0.5"
+
+ripemd160@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-1.0.1.tgz#93a4bbd4942bc574b69a8fa57c71de10ecca7d6e"
+
+run-async@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/run-async/-/run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389"
+ dependencies:
+ once "^1.3.0"
+
+rx-lite@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102"
+
+safe-buffer@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7"
+
+select-hose@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
+
+select2@3.5.2-browserify:
+ version "3.5.2-browserify"
+ resolved "https://registry.yarnpkg.com/select2/-/select2-3.5.2-browserify.tgz#dc4dafda38d67a734e8a97a46f0d3529ae05391d"
+
+"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@~5.3.0:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
+
+semver@~4.3.3:
+ version "4.3.6"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da"
+
+send@0.14.2:
+ version "0.14.2"
+ resolved "https://registry.yarnpkg.com/send/-/send-0.14.2.tgz#39b0438b3f510be5dc6f667a11f71689368cdeef"
+ dependencies:
+ debug "~2.2.0"
+ depd "~1.1.0"
+ destroy "~1.0.4"
+ encodeurl "~1.0.1"
+ escape-html "~1.0.3"
+ etag "~1.7.0"
+ fresh "0.3.0"
+ http-errors "~1.5.1"
+ mime "1.3.4"
+ ms "0.7.2"
+ on-finished "~2.3.0"
+ range-parser "~1.2.0"
+ statuses "~1.3.1"
+
+serve-index@^1.7.2:
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.8.0.tgz#7c5d96c13fb131101f93c1c5774f8516a1e78d3b"
+ dependencies:
+ accepts "~1.3.3"
+ batch "0.5.3"
+ debug "~2.2.0"
+ escape-html "~1.0.3"
+ http-errors "~1.5.0"
+ mime-types "~2.1.11"
+ parseurl "~1.3.1"
+
+serve-static@~1.11.2:
+ version "1.11.2"
+ resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.11.2.tgz#2cf9889bd4435a320cc36895c9aa57bd662e6ac7"
+ dependencies:
+ encodeurl "~1.0.1"
+ escape-html "~1.0.3"
+ parseurl "~1.3.1"
+ send "0.14.2"
+
+set-blocking@^2.0.0, set-blocking@~2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
+
+set-immediate-shim@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61"
+
+setimmediate@^1.0.4:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
+
+setprototypeof@1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.2.tgz#81a552141ec104b88e89ce383103ad5c66564d08"
+
+sha.js@^2.3.6:
+ version "2.4.8"
+ resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.8.tgz#37068c2c476b6baf402d14a49c67f597921f634f"
+ dependencies:
+ inherits "^2.0.1"
+
+shelljs@^0.7.5:
+ version "0.7.6"
+ resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.6.tgz#379cccfb56b91c8601e4793356eb5382924de9ad"
+ dependencies:
+ glob "^7.0.0"
+ interpret "^1.0.0"
+ rechoir "^0.6.2"
+
+signal-exit@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
+
+slash@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
+
+slice-ansi@0.0.4:
+ version "0.0.4"
+ resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"
+
+sntp@1.x.x:
+ version "1.0.9"
+ resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198"
+ dependencies:
+ hoek "2.x.x"
+
+socket.io-adapter@0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-0.5.0.tgz#cb6d4bb8bec81e1078b99677f9ced0046066bb8b"
+ dependencies:
+ debug "2.3.3"
+ socket.io-parser "2.3.1"
+
+socket.io-client@1.7.2:
+ version "1.7.2"
+ resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-1.7.2.tgz#39fdb0c3dd450e321b7e40cfd83612ec533dd644"
+ dependencies:
+ backo2 "1.0.2"
+ component-bind "1.0.0"
+ component-emitter "1.2.1"
+ debug "2.3.3"
+ engine.io-client "1.8.2"
+ has-binary "0.1.7"
+ indexof "0.0.1"
+ object-component "0.0.3"
+ parseuri "0.0.5"
+ socket.io-parser "2.3.1"
+ to-array "0.1.4"
+
+socket.io-parser@2.3.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-2.3.1.tgz#dd532025103ce429697326befd64005fcfe5b4a0"
+ dependencies:
+ component-emitter "1.1.2"
+ debug "2.2.0"
+ isarray "0.0.1"
+ json3 "3.3.2"
+
+socket.io@1.7.2:
+ version "1.7.2"
+ resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-1.7.2.tgz#83bbbdf2e79263b378900da403e7843e05dc3b71"
+ dependencies:
+ debug "2.3.3"
+ engine.io "1.8.2"
+ has-binary "0.1.7"
+ object-assign "4.1.0"
+ socket.io-adapter "0.5.0"
+ socket.io-client "1.7.2"
+ socket.io-parser "2.3.1"
+
+sockjs-client@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.1.tgz#284843e9a9784d7c474b1571b3240fca9dda4bb0"
+ dependencies:
+ debug "^2.2.0"
+ eventsource "~0.1.6"
+ faye-websocket "~0.11.0"
+ inherits "^2.0.1"
+ json3 "^3.3.2"
+ url-parse "^1.1.1"
+
+sockjs@0.3.18:
+ version "0.3.18"
+ resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.18.tgz#d9b289316ca7df77595ef299e075f0f937eb4207"
+ dependencies:
+ faye-websocket "^0.10.0"
+ uuid "^2.0.2"
+
+source-list-map@~0.1.7:
+ version "0.1.8"
+ resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.8.tgz#c550b2ab5427f6b3f21f5afead88c4f5587b2106"
+
+source-map-support@^0.4.2:
+ version "0.4.11"
+ resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.11.tgz#647f939978b38535909530885303daf23279f322"
+ dependencies:
+ source-map "^0.5.3"
+
+source-map@^0.1.41:
+ version "0.1.43"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346"
+ dependencies:
+ amdefine ">=0.0.4"
+
+source-map@^0.4.4:
+ version "0.4.4"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b"
+ dependencies:
+ amdefine ">=0.0.4"
+
+source-map@^0.5.0, source-map@^0.5.3, source-map@~0.5.1, source-map@~0.5.3:
+ version "0.5.6"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
+
+source-map@~0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d"
+ dependencies:
+ amdefine ">=0.0.4"
+
+spdx-correct@~1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40"
+ dependencies:
+ spdx-license-ids "^1.0.2"
+
+spdx-expression-parse@~1.0.0:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c"
+
+spdx-license-ids@^1.0.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57"
+
+spdy-transport@^2.0.15:
+ version "2.0.18"
+ resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-2.0.18.tgz#43fc9c56be2cccc12bb3e2754aa971154e836ea6"
+ dependencies:
+ debug "^2.2.0"
+ hpack.js "^2.1.6"
+ obuf "^1.1.0"
+ readable-stream "^2.0.1"
+ wbuf "^1.4.0"
+
+spdy@^3.4.1:
+ version "3.4.4"
+ resolved "https://registry.yarnpkg.com/spdy/-/spdy-3.4.4.tgz#e0406407ca90ff01b553eb013505442649f5a819"
+ dependencies:
+ debug "^2.2.0"
+ handle-thing "^1.2.4"
+ http-deceiver "^1.2.4"
+ select-hose "^2.0.0"
+ spdy-transport "^2.0.15"
+
+sprintf-js@~1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
+
+sshpk@^1.7.0:
+ version "1.10.2"
+ resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.10.2.tgz#d5a804ce22695515638e798dbe23273de070a5fa"
+ dependencies:
+ asn1 "~0.2.3"
+ assert-plus "^1.0.0"
+ dashdash "^1.12.0"
+ getpass "^0.1.1"
+ optionalDependencies:
+ bcrypt-pbkdf "^1.0.0"
+ ecc-jsbn "~0.1.1"
+ jodid25519 "^1.0.0"
+ jsbn "~0.1.0"
+ tweetnacl "~0.14.0"
+
+stats-webpack-plugin@^0.4.3:
+ version "0.4.3"
+ resolved "https://registry.yarnpkg.com/stats-webpack-plugin/-/stats-webpack-plugin-0.4.3.tgz#b2f618202f28dd04ab47d7ecf54ab846137b7aea"
+
+"statuses@>= 1.3.1 < 2", statuses@~1.3.0, statuses@~1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e"
+
+stream-browserify@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db"
+ dependencies:
+ inherits "~2.0.1"
+ readable-stream "^2.0.2"
+
+stream-http@^2.3.1:
+ version "2.6.3"
+ resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.6.3.tgz#4c3ddbf9635968ea2cfd4e48d43de5def2625ac3"
+ dependencies:
+ builtin-status-codes "^3.0.0"
+ inherits "^2.0.1"
+ readable-stream "^2.1.0"
+ to-arraybuffer "^1.0.0"
+ xtend "^4.0.0"
+
+string-width@^1.0.1, string-width@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
+ dependencies:
+ code-point-at "^1.0.0"
+ is-fullwidth-code-point "^1.0.0"
+ strip-ansi "^3.0.0"
+
+string-width@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.0.0.tgz#635c5436cc72a6e0c387ceca278d4e2eec52687e"
+ dependencies:
+ is-fullwidth-code-point "^2.0.0"
+ strip-ansi "^3.0.0"
+
+string_decoder@^0.10.25, string_decoder@~0.10.x:
+ version "0.10.31"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
+
+stringstream@~0.0.4:
+ version "0.0.5"
+ resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878"
+
+strip-ansi@^3.0.0, strip-ansi@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
+ dependencies:
+ ansi-regex "^2.0.0"
+
+strip-bom@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
+ dependencies:
+ is-utf8 "^0.2.0"
+
+strip-bom@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
+
+strip-json-comments@~1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91"
+
+strip-json-comments@~2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
+
+supports-color@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-0.2.0.tgz#d92de2694eb3f67323973d7ae3d8b55b4c22190a"
+
+supports-color@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
+
+supports-color@^3.1.0, supports-color@^3.1.1, supports-color@^3.1.2:
+ version "3.2.3"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6"
+ dependencies:
+ has-flag "^1.0.0"
+
+table@^3.7.8:
+ version "3.8.3"
+ resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f"
+ dependencies:
+ ajv "^4.7.0"
+ ajv-keywords "^1.0.0"
+ chalk "^1.1.1"
+ lodash "^4.0.0"
+ slice-ansi "0.0.4"
+ string-width "^2.0.0"
+
+tapable@^0.1.8:
+ version "0.1.10"
+ resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.1.10.tgz#29c35707c2b70e50d07482b5d202e8ed446dafd4"
+
+tapable@^0.2.5, tapable@~0.2.5:
+ version "0.2.6"
+ resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.6.tgz#206be8e188860b514425375e6f1ae89bfb01fd8d"
+
+tar-pack@~3.3.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.3.0.tgz#30931816418f55afc4d21775afdd6720cee45dae"
+ dependencies:
+ debug "~2.2.0"
+ fstream "~1.0.10"
+ fstream-ignore "~1.0.5"
+ once "~1.3.3"
+ readable-stream "~2.1.4"
+ rimraf "~2.5.1"
+ tar "~2.2.1"
+ uid-number "~0.0.6"
+
+tar@~2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1"
+ dependencies:
+ block-stream "*"
+ fstream "^1.0.2"
+ inherits "2"
+
+test-exclude@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.0.0.tgz#0ddc0100b8ae7e88b34eb4fd98a907e961991900"
+ dependencies:
+ arrify "^1.0.1"
+ micromatch "^2.3.11"
+ object-assign "^4.1.0"
+ read-pkg-up "^1.0.1"
+ require-main-filename "^1.0.1"
+
+text-table@~0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
+
+throttleit@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c"
+
+through@^2.3.6:
+ version "2.3.8"
+ resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
+
+timeago.js@^2.0.5:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/timeago.js/-/timeago.js-2.0.5.tgz#730c74fbdb0b0917a553675a4460e3a7f80db86c"
+
+timers-browserify@^1.4.2:
+ version "1.4.2"
+ resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-1.4.2.tgz#c9c58b575be8407375cb5e2462dacee74359f41d"
+ dependencies:
+ process "~0.11.0"
+
+timers-browserify@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.2.tgz#ab4883cf597dcd50af211349a00fbca56ac86b86"
+ dependencies:
+ setimmediate "^1.0.4"
+
+tmp@0.0.28, tmp@0.0.x:
+ version "0.0.28"
+ resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.28.tgz#172735b7f614ea7af39664fa84cf0de4e515d120"
+ dependencies:
+ os-tmpdir "~1.0.1"
+
+to-array@0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890"
+
+to-arraybuffer@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
+
+to-fast-properties@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.2.tgz#f3f5c0c3ba7299a7ef99427e44633257ade43320"
+
+tough-cookie@~2.3.0:
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a"
+ dependencies:
+ punycode "^1.4.1"
+
+trim-right@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
+
+tryit@^1.0.1:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/tryit/-/tryit-1.0.3.tgz#393be730a9446fd1ead6da59a014308f36c289cb"
+
+tty-browserify@0.0.0:
+ version "0.0.0"
+ resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
+
+tunnel-agent@~0.4.1:
+ version "0.4.3"
+ resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb"
+
+tweetnacl@^0.14.3, tweetnacl@~0.14.0:
+ version "0.14.5"
+ resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
+
+type-check@~0.3.2:
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"
+ dependencies:
+ prelude-ls "~1.1.2"
+
+type-is@~1.6.14:
+ version "1.6.14"
+ resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.14.tgz#e219639c17ded1ca0789092dd54a03826b817cb2"
+ dependencies:
+ media-typer "0.3.0"
+ mime-types "~2.1.13"
+
+typedarray@^0.0.6, typedarray@~0.0.5:
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
+
+uglify-js@^2.6, uglify-js@^2.7.5:
+ version "2.7.5"
+ resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.7.5.tgz#4612c0c7baaee2ba7c487de4904ae122079f2ca8"
+ dependencies:
+ async "~0.2.6"
+ source-map "~0.5.1"
+ uglify-to-browserify "~1.0.0"
+ yargs "~3.10.0"
+
+uglify-to-browserify@~1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
+
+uid-number@~0.0.6:
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
+
+ultron@1.0.x:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa"
+
+unc-path-regex@^0.1.0:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa"
+
+underscore@^1.8.3:
+ version "1.8.3"
+ resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022"
+
+unpipe@1.0.0, unpipe@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
+
+url-parse@1.0.x:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.0.5.tgz#0854860422afdcfefeb6c965c662d4800169927b"
+ dependencies:
+ querystringify "0.0.x"
+ requires-port "1.0.x"
+
+url-parse@^1.1.1:
+ version "1.1.7"
+ resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.1.7.tgz#025cff999653a459ab34232147d89514cc87d74a"
+ dependencies:
+ querystringify "0.0.x"
+ requires-port "1.0.x"
+
+url@^0.11.0:
+ version "0.11.0"
+ resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
+ dependencies:
+ punycode "1.3.2"
+ querystring "0.2.0"
+
+user-home@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/user-home/-/user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f"
+ dependencies:
+ os-homedir "^1.0.0"
+
+useragent@^2.1.10:
+ version "2.1.12"
+ resolved "https://registry.yarnpkg.com/useragent/-/useragent-2.1.12.tgz#aa7da6cdc48bdc37ba86790871a7321d64edbaa2"
+ dependencies:
+ lru-cache "2.2.x"
+ tmp "0.0.x"
+
+util-deprecate@~1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
+
+util@0.10.3, util@^0.10.3:
+ version "0.10.3"
+ resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9"
+ dependencies:
+ inherits "2.0.1"
+
+utils-merge@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8"
+
+uuid@^2.0.2:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a"
+
+uuid@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1"
+
+validate-npm-package-license@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc"
+ dependencies:
+ spdx-correct "~1.0.0"
+ spdx-expression-parse "~1.0.0"
+
+vary@~1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.0.tgz#e1e5affbbd16ae768dd2674394b9ad3022653140"
+
+verror@1.3.6:
+ version "1.3.6"
+ resolved "https://registry.yarnpkg.com/verror/-/verror-1.3.6.tgz#cff5df12946d297d2baaefaa2689e25be01c005c"
+ dependencies:
+ extsprintf "1.0.2"
+
+vm-browserify@0.0.4:
+ version "0.0.4"
+ resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73"
+ dependencies:
+ indexof "0.0.1"
+
+void-elements@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
+
+vue-resource@^0.9.3:
+ version "0.9.3"
+ resolved "https://registry.yarnpkg.com/vue-resource/-/vue-resource-0.9.3.tgz#ab46e1c44ea219142dcc28ae4043b3b04c80959d"
+
+vue@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/vue/-/vue-2.0.3.tgz#3f7698f83d6ad1f0e35955447901672876c63fde"
+
+watchpack@^1.2.0:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.2.1.tgz#01efa80c5c29e5c56ba55d6f5470a35b6402f0b2"
+ dependencies:
+ async "^2.1.2"
+ chokidar "^1.4.3"
+ graceful-fs "^4.1.2"
+
+wbuf@^1.1.0, wbuf@^1.4.0:
+ version "1.7.2"
+ resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.2.tgz#d697b99f1f59512df2751be42769c1580b5801fe"
+ dependencies:
+ minimalistic-assert "^1.0.0"
+
+webpack-bundle-analyzer@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-2.3.0.tgz#0d05e96a43033f7cc57f6855b725782ba61e93a4"
+ dependencies:
+ acorn "^4.0.11"
+ chalk "^1.1.3"
+ commander "^2.9.0"
+ ejs "^2.5.5"
+ express "^4.14.1"
+ filesize "^3.5.4"
+ gzip-size "^3.0.0"
+ lodash "^4.17.4"
+ mkdirp "^0.5.1"
+ opener "^1.4.2"
+
+webpack-dev-middleware@^1.0.11, webpack-dev-middleware@^1.9.0:
+ version "1.10.0"
+ resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.10.0.tgz#7d5be2651e692fddfafd8aaed177c16ff51f0eb8"
+ dependencies:
+ memory-fs "~0.4.1"
+ mime "^1.3.4"
+ path-is-absolute "^1.0.0"
+ range-parser "^1.0.3"
+
+webpack-dev-server@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.3.0.tgz#0437704bbd4d941a6e4c061eb3cc232ed7d06101"
+ dependencies:
+ ansi-html "0.0.7"
+ chokidar "^1.6.0"
+ compression "^1.5.2"
+ connect-history-api-fallback "^1.3.0"
+ express "^4.13.3"
+ html-entities "^1.2.0"
+ http-proxy-middleware "~0.17.1"
+ opn "4.0.2"
+ portfinder "^1.0.9"
+ serve-index "^1.7.2"
+ sockjs "0.3.18"
+ sockjs-client "1.1.1"
+ spdy "^3.4.1"
+ strip-ansi "^3.0.0"
+ supports-color "^3.1.1"
+ webpack-dev-middleware "^1.9.0"
+ yargs "^6.0.0"
+
+webpack-sources@^0.1.0, webpack-sources@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-0.1.4.tgz#ccc2c817e08e5fa393239412690bb481821393cd"
+ dependencies:
+ source-list-map "~0.1.7"
+ source-map "~0.5.3"
+
+webpack@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-2.2.1.tgz#7bb1d72ae2087dd1a4af526afec15eed17dda475"
+ dependencies:
+ acorn "^4.0.4"
+ acorn-dynamic-import "^2.0.0"
+ ajv "^4.7.0"
+ ajv-keywords "^1.1.1"
+ async "^2.1.2"
+ enhanced-resolve "^3.0.0"
+ interpret "^1.0.0"
+ json-loader "^0.5.4"
+ loader-runner "^2.3.0"
+ loader-utils "^0.2.16"
+ memory-fs "~0.4.1"
+ mkdirp "~0.5.0"
+ node-libs-browser "^2.0.0"
+ source-map "^0.5.3"
+ supports-color "^3.1.0"
+ tapable "~0.2.5"
+ uglify-js "^2.7.5"
+ watchpack "^1.2.0"
+ webpack-sources "^0.1.4"
+ yargs "^6.0.0"
+
+websocket-driver@>=0.5.1:
+ version "0.6.5"
+ resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.6.5.tgz#5cb2556ceb85f4373c6d8238aa691c8454e13a36"
+ dependencies:
+ websocket-extensions ">=0.1.1"
+
+websocket-extensions@>=0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.1.tgz#76899499c184b6ef754377c2dbb0cd6cb55d29e7"
+
+which-module@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"
+
+which@^1.1.1, which@~1.2.10:
+ version "1.2.12"
+ resolved "https://registry.yarnpkg.com/which/-/which-1.2.12.tgz#de67b5e450269f194909ef23ece4ebe416fa1192"
+ dependencies:
+ isexe "^1.1.1"
+
+wide-align@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.0.tgz#40edde802a71fea1f070da3e62dcda2e7add96ad"
+ dependencies:
+ string-width "^1.0.1"
+
+window-size@0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"
+
+wordwrap@0.0.2:
+ version "0.0.2"
+ resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
+
+wordwrap@^1.0.0, wordwrap@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
+
+wordwrap@~0.0.2:
+ version "0.0.3"
+ resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
+
+wrap-ansi@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
+ dependencies:
+ string-width "^1.0.1"
+ strip-ansi "^3.0.1"
+
+wrappy@1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+
+write@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757"
+ dependencies:
+ mkdirp "^0.5.1"
+
+ws@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.1.tgz#082ddb6c641e85d4bb451f03d52f06eabdb1f018"
+ dependencies:
+ options ">=0.0.5"
+ ultron "1.0.x"
+
+wtf-8@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/wtf-8/-/wtf-8-1.0.0.tgz#392d8ba2d0f1c34d1ee2d630f15d0efb68e1048a"
+
+xmlhttprequest-ssl@1.5.3:
+ version "1.5.3"
+ resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz#185a888c04eca46c3e4070d99f7b49de3528992d"
+
+xtend@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
+
+y18n@^3.2.1:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
+
+yargs-parser@^4.2.0:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-4.2.1.tgz#29cceac0dc4f03c6c87b4a9f217dd18c9f74871c"
+ dependencies:
+ camelcase "^3.0.0"
+
+yargs@^6.0.0:
+ version "6.6.0"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208"
+ dependencies:
+ camelcase "^3.0.0"
+ cliui "^3.2.0"
+ decamelize "^1.1.1"
+ get-caller-file "^1.0.1"
+ os-locale "^1.4.0"
+ read-pkg-up "^1.0.1"
+ require-directory "^2.1.1"
+ require-main-filename "^1.0.1"
+ set-blocking "^2.0.0"
+ string-width "^1.0.2"
+ which-module "^1.0.0"
+ y18n "^3.2.1"
+ yargs-parser "^4.2.0"
+
+yargs@~3.10.0:
+ version "3.10.0"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1"
+ dependencies:
+ camelcase "^1.0.2"
+ cliui "^2.1.0"
+ decamelize "^1.0.0"
+ window-size "0.1.0"
+
+yauzl@2.4.1:
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.4.1.tgz#9528f442dab1b2284e58b4379bb194e22e0c4005"
+ dependencies:
+ fd-slicer "~1.0.1"
+
+yeast@0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419"