summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.eslintrc4
-rw-r--r--.gitlab-ci.yml6
-rw-r--r--.gitlab/issue_templates/Bug.md8
-rw-r--r--CHANGELOG.md6
-rw-r--r--Gemfile3
-rw-r--r--Gemfile.lock5
-rw-r--r--PROCESS.md13
-rwxr-xr-xapp/assets/images/ci_favicons/favicon_status_canceled.ico (renamed from app/assets/images/ci_favicons/icon_status_canceled.ico)bin5430 -> 5430 bytes
-rwxr-xr-xapp/assets/images/ci_favicons/favicon_status_created.ico (renamed from app/assets/images/ci_favicons/icon_status_created.ico)bin5430 -> 5430 bytes
-rwxr-xr-xapp/assets/images/ci_favicons/favicon_status_failed.ico (renamed from app/assets/images/ci_favicons/icon_status_failed.ico)bin5430 -> 5430 bytes
-rwxr-xr-xapp/assets/images/ci_favicons/favicon_status_manual.ico (renamed from app/assets/images/ci_favicons/icon_status_manual.ico)bin5430 -> 5430 bytes
-rwxr-xr-xapp/assets/images/ci_favicons/favicon_status_not_found.ico (renamed from app/assets/images/ci_favicons/icon_status_not_found.ico)bin5430 -> 5430 bytes
-rwxr-xr-xapp/assets/images/ci_favicons/favicon_status_pending.ico (renamed from app/assets/images/ci_favicons/icon_status_pending.ico)bin5430 -> 5430 bytes
-rwxr-xr-xapp/assets/images/ci_favicons/favicon_status_running.ico (renamed from app/assets/images/ci_favicons/icon_status_running.ico)bin5430 -> 5430 bytes
-rwxr-xr-xapp/assets/images/ci_favicons/favicon_status_skipped.ico (renamed from app/assets/images/ci_favicons/icon_status_skipped.ico)bin5430 -> 5430 bytes
-rwxr-xr-xapp/assets/images/ci_favicons/favicon_status_success.ico (renamed from app/assets/images/ci_favicons/icon_status_success.ico)bin5430 -> 5430 bytes
-rwxr-xr-xapp/assets/images/ci_favicons/favicon_status_warning.ico (renamed from app/assets/images/ci_favicons/icon_status_warning.ico)bin5430 -> 5430 bytes
-rw-r--r--app/assets/javascripts/awards_handler.js97
-rw-r--r--app/assets/javascripts/behaviors/autosize.js45
-rw-r--r--app/assets/javascripts/behaviors/details_behavior.js45
-rw-r--r--app/assets/javascripts/behaviors/index.js9
-rw-r--r--app/assets/javascripts/behaviors/quick_submit.js109
-rw-r--r--app/assets/javascripts/behaviors/requires_input.js90
-rw-r--r--app/assets/javascripts/behaviors/toggler_behavior.js84
-rw-r--r--app/assets/javascripts/boards/boards_bundle.js1
-rw-r--r--app/assets/javascripts/boards/components/board.js168
-rw-r--r--app/assets/javascripts/boards/components/board_delete.js28
-rw-r--r--app/assets/javascripts/boards/components/board_sidebar.js112
-rw-r--r--app/assets/javascripts/boards/components/issue_card_inner.js246
-rw-r--r--app/assets/javascripts/boards/components/modal/empty_state.js120
-rw-r--r--app/assets/javascripts/boards/components/modal/footer.js126
-rw-r--r--app/assets/javascripts/boards/components/modal/header.js130
-rw-r--r--app/assets/javascripts/boards/components/modal/index.js272
-rw-r--r--app/assets/javascripts/boards/components/modal/list.js268
-rw-r--r--app/assets/javascripts/boards/components/modal/lists_dropdown.js102
-rw-r--r--app/assets/javascripts/boards/components/modal/tabs.js86
-rw-r--r--app/assets/javascripts/boards/components/new_list_dropdown.js120
-rw-r--r--app/assets/javascripts/boards/components/sidebar/remove_issue.js98
-rw-r--r--app/assets/javascripts/boards/mixins/modal_mixins.js22
-rw-r--r--app/assets/javascripts/boards/mixins/sortable_default_options.js60
-rw-r--r--app/assets/javascripts/boards/models/list.js11
-rw-r--r--app/assets/javascripts/boards/stores/boards_store.js216
-rw-r--r--app/assets/javascripts/boards/stores/modal_store.js156
-rw-r--r--app/assets/javascripts/build.js26
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_table.js4
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_code_component.js81
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_issue_component.js85
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_plan_component.js85
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_production_component.js85
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_review_component.js103
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_staging_component.js85
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_test_component.js85
-rw-r--r--app/assets/javascripts/cycle_analytics/components/total_time_component.js41
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js2
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_service.js64
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_store.js171
-rw-r--r--app/assets/javascripts/diff_notes/components/resolve_btn.js8
-rw-r--r--app/assets/javascripts/dispatcher.js10
-rw-r--r--app/assets/javascripts/dropzone_input.js10
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js2
-rw-r--r--app/assets/javascripts/group.js21
-rw-r--r--app/assets/javascripts/issue.js108
-rw-r--r--app/assets/javascripts/issue_show/index.js36
-rw-r--r--app/assets/javascripts/issue_show/issue_title.vue (renamed from app/assets/javascripts/issue_show/issue_title.js)9
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js14
-rw-r--r--app/assets/javascripts/lib/utils/constants.js2
-rw-r--r--app/assets/javascripts/lib/utils/number_utils.js12
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js346
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js171
-rw-r--r--app/assets/javascripts/main.js9
-rw-r--r--app/assets/javascripts/merged_buttons.js12
-rw-r--r--app/assets/javascripts/shortcuts_wiki.js16
-rw-r--r--app/assets/javascripts/user_callout.js2
-rw-r--r--app/assets/javascripts/vue_pipelines_index/components/async_button.vue (renamed from app/assets/javascripts/vue_pipelines_index/components/async_button.js)49
-rw-r--r--app/assets/javascripts/vue_pipelines_index/components/empty_state.js33
-rw-r--r--app/assets/javascripts/vue_pipelines_index/components/empty_state.vue34
-rw-r--r--app/assets/javascripts/vue_pipelines_index/components/error_state.js19
-rw-r--r--app/assets/javascripts/vue_pipelines_index/components/error_state.vue21
-rw-r--r--app/assets/javascripts/vue_pipelines_index/pipelines.js4
-rw-r--r--app/assets/javascripts/vue_shared/components/pipelines_table_row.js2
-rw-r--r--app/assets/stylesheets/framework/awards.scss20
-rw-r--r--app/assets/stylesheets/framework/calendar.scss2
-rw-r--r--app/assets/stylesheets/framework/filters.scss28
-rw-r--r--app/assets/stylesheets/framework/header.scss4
-rw-r--r--app/assets/stylesheets/framework/typography.scss1
-rw-r--r--app/assets/stylesheets/framework/variables.scss30
-rw-r--r--app/assets/stylesheets/pages/builds.scss19
-rw-r--r--app/assets/stylesheets/pages/commits.scss7
-rw-r--r--app/assets/stylesheets/pages/diff.scss4
-rw-r--r--app/assets/stylesheets/pages/events.scss22
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss3
-rw-r--r--app/assets/stylesheets/pages/notes.scss53
-rw-r--r--app/assets/stylesheets/pages/profile.scss59
-rw-r--r--app/assets/stylesheets/pages/projects.scss28
-rw-r--r--app/controllers/admin/application_controller.rb2
-rw-r--r--app/controllers/admin/impersonations_controller.rb2
-rw-r--r--app/controllers/admin/spam_logs_controller.rb2
-rw-r--r--app/controllers/projects/compare_controller.rb1
-rw-r--r--app/controllers/projects/hooks_controller.rb4
-rwxr-xr-xapp/controllers/projects/merge_requests_controller.rb78
-rw-r--r--app/controllers/projects_controller.rb10
-rw-r--r--app/controllers/registrations_controller.rb2
-rw-r--r--app/helpers/blob_helper.rb10
-rw-r--r--app/helpers/diff_helper.rb2
-rw-r--r--app/helpers/events_helper.rb28
-rw-r--r--app/helpers/issues_helper.rb8
-rw-r--r--app/helpers/javascript_helper.rb5
-rw-r--r--app/helpers/notes_helper.rb19
-rw-r--r--app/helpers/preferences_helper.rb6
-rw-r--r--app/helpers/projects_helper.rb19
-rw-r--r--app/helpers/snippets_helper.rb2
-rw-r--r--app/helpers/visibility_level_helper.rb2
-rw-r--r--app/helpers/webpack_helper.rb30
-rw-r--r--app/models/abuse_report.rb2
-rw-r--r--app/models/blob.rb30
-rw-r--r--app/models/ci/trigger.rb2
-rw-r--r--app/models/commit.rb15
-rw-r--r--app/models/concerns/discussion_on_diff.rb12
-rw-r--r--app/models/concerns/note_on_diff.rb14
-rw-r--r--app/models/concerns/noteable.rb4
-rw-r--r--app/models/container_repository.rb7
-rw-r--r--app/models/diff_discussion.rb1
-rw-r--r--app/models/diff_note.rb14
-rw-r--r--app/models/issue.rb2
-rw-r--r--app/models/label.rb6
-rw-r--r--app/models/legacy_diff_discussion.rb8
-rw-r--r--app/models/legacy_diff_note.rb3
-rw-r--r--app/models/merge_request.rb9
-rw-r--r--app/models/merge_request_diff.rb10
-rw-r--r--app/models/note.rb9
-rw-r--r--app/models/project_services/chat_notification_service.rb2
-rw-r--r--app/models/repository.rb28
-rw-r--r--app/models/spam_log.rb4
-rw-r--r--app/models/user.rb8
-rw-r--r--app/policies/ci/runner_policy.rb2
-rw-r--r--app/policies/group_policy.rb1
-rw-r--r--app/serializers/status_entity.rb6
-rw-r--r--app/services/projects/create_service.rb3
-rw-r--r--app/services/projects/import_service.rb1
-rw-r--r--app/services/search/global_service.rb11
-rw-r--r--app/services/search/group_service.rb18
-rw-r--r--app/services/search_service.rb2
-rw-r--r--app/services/users/build_service.rb100
-rw-r--r--app/services/users/create_service.rb95
-rw-r--r--app/services/users/destroy_service.rb2
-rw-r--r--app/uploaders/file_uploader.rb4
-rw-r--r--app/views/admin/application_settings/_form.html.haml1
-rw-r--r--app/views/admin/health_check/show.html.haml2
-rw-r--r--app/views/admin/runners/index.html.haml2
-rw-r--r--app/views/admin/services/index.html.haml2
-rw-r--r--app/views/award_emoji/_awards_block.html.haml4
-rw-r--r--app/views/discussions/_discussion.html.haml13
-rw-r--r--app/views/events/event/_common.html.haml11
-rw-r--r--app/views/events/event/_created_project.html.haml3
-rw-r--r--app/views/events/event/_note.html.haml3
-rw-r--r--app/views/events/event/_push.html.haml6
-rw-r--r--app/views/groups/subgroups.html.haml2
-rw-r--r--app/views/help/_shortcuts.html.haml8
-rw-r--r--app/views/help/ui.html.haml8
-rw-r--r--app/views/layouts/_head.html.haml6
-rw-r--r--app/views/layouts/header/_default.html.haml2
-rw-r--r--app/views/layouts/nav/_explore.html.haml19
-rw-r--r--app/views/layouts/nav/_project.html.haml4
-rw-r--r--app/views/notify/project_was_exported_email.html.haml2
-rw-r--r--app/views/projects/blob/_blob.html.haml15
-rw-r--r--app/views/projects/blob/_header.html.haml9
-rw-r--r--app/views/projects/blob/_image.html.haml15
-rw-r--r--app/views/projects/blob/_markup.html.haml4
-rw-r--r--app/views/projects/blob/_svg.html.haml9
-rw-r--r--app/views/projects/blob/_text.html.haml21
-rw-r--r--app/views/projects/blob/_too_large.html.haml5
-rw-r--r--app/views/projects/blob/show.html.haml2
-rw-r--r--app/views/projects/builds/_sidebar.html.haml2
-rw-r--r--app/views/projects/builds/show.html.haml10
-rw-r--r--app/views/projects/ci/builds/_build.html.haml2
-rw-r--r--app/views/projects/diffs/_content.html.haml2
-rw-r--r--app/views/projects/diffs/_parallel_view.html.haml3
-rw-r--r--app/views/projects/diffs/_text_file.html.haml3
-rw-r--r--app/views/projects/issues/show.html.haml1
-rw-r--r--app/views/projects/labels/edit.html.haml2
-rw-r--r--app/views/projects/labels/index.html.haml2
-rw-r--r--app/views/projects/labels/new.html.haml2
-rw-r--r--app/views/projects/merge_requests/_head.html.haml21
-rw-r--r--app/views/projects/merge_requests/_new_submit.html.haml2
-rw-r--r--app/views/projects/merge_requests/index.html.haml3
-rw-r--r--app/views/projects/merge_requests/show/_pipelines.html.haml3
-rw-r--r--app/views/projects/merge_requests/show/_versions.html.haml11
-rw-r--r--app/views/projects/merge_requests/widget/open/_accept.html.haml2
-rw-r--r--app/views/projects/milestones/edit.html.haml2
-rw-r--r--app/views/projects/milestones/index.html.haml2
-rw-r--r--app/views/projects/milestones/new.html.haml2
-rw-r--r--app/views/projects/milestones/show.html.haml2
-rw-r--r--app/views/projects/notes/_note.html.haml36
-rw-r--r--app/views/projects/registry/repositories/_image.html.haml2
-rw-r--r--app/views/projects/registry/repositories/_tag.html.haml2
-rw-r--r--app/views/projects/show.html.haml8
-rw-r--r--app/views/projects/snippets/edit.html.haml2
-rw-r--r--app/views/projects/snippets/show.html.haml2
-rw-r--r--app/views/projects/variables/_table.html.haml2
-rw-r--r--app/views/projects/wikis/_main_links.html.haml2
-rw-r--r--app/views/search/results/_issue.html.haml5
-rw-r--r--app/views/search/results/_merge_request.html.haml9
-rw-r--r--app/views/shared/_group_form.html.haml18
-rw-r--r--app/views/shared/_import_form.html.haml2
-rw-r--r--app/views/shared/_mr_head.html.haml4
-rw-r--r--app/views/shared/_user_callout.html.haml17
-rw-r--r--app/views/shared/empty_states/_issues.html.haml6
-rw-r--r--app/views/shared/icons/_icon_merged.svg2
-rw-r--r--app/views/shared/icons/_icon_trash_o.svg (renamed from app/views/shared/icons/_trash_o.svg)0
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml15
-rw-r--r--app/views/snippets/edit.html.haml2
-rw-r--r--app/views/snippets/show.html.haml2
-rw-r--r--app/workers/build_coverage_worker.rb3
-rw-r--r--changelogs/unreleased/2120-issues-search-bar-not-picking-up.yml4
-rw-r--r--changelogs/unreleased/22714-update-all-instances-of-fa-refresh.yml4
-rw-r--r--changelogs/unreleased/27655-clear-emoji-search-after-selection.yml4
-rw-r--r--changelogs/unreleased/27729-improve-webpack-dev-environment.yml4
-rw-r--r--changelogs/unreleased/28017-separate-ce-params-on-api.yml4
-rw-r--r--changelogs/unreleased/28575-expand-collapse-look.yml4
-rw-r--r--changelogs/unreleased/29056-backport-ee-cleanup-database-file.yml4
-rw-r--r--changelogs/unreleased/29595-customize-experience-callout.yml4
-rw-r--r--changelogs/unreleased/29816-create-keyboard-shortcut-for-editing-wiki-page.yml4
-rw-r--r--changelogs/unreleased/29866-navbar-counters.yml4
-rw-r--r--changelogs/unreleased/30024-owner-can-t-initialize-git-repo-for-new-project-in-group.yml4
-rw-r--r--changelogs/unreleased/30349-create-users-build-service.yml4
-rw-r--r--changelogs/unreleased/30457-expire-note-destroy.yml4
-rw-r--r--changelogs/unreleased/30678-improve-dev-server-process.yml4
-rw-r--r--changelogs/unreleased/30779-show-mr-subnav-issue-tracker.yml4
-rw-r--r--changelogs/unreleased/31009-disable-test-settings-on-services-when-repository-is-empty.yml4
-rw-r--r--changelogs/unreleased/6260-frontend-prevent-authored-votes.yml4
-rw-r--r--changelogs/unreleased/add-field-for-group-name.yml4
-rw-r--r--changelogs/unreleased/add-vue-loader.yml4
-rw-r--r--changelogs/unreleased/bb_save_trace.yml5
-rw-r--r--changelogs/unreleased/boards-done-add-tooltip.yml4
-rw-r--r--changelogs/unreleased/clean_carrierwave_tempfiles.yml4
-rw-r--r--changelogs/unreleased/dm-link-discussion-to-outdated-diff.yml4
-rw-r--r--changelogs/unreleased/dz-fix-project-view.yml4
-rw-r--r--changelogs/unreleased/dz-remove-repo-version.yml4
-rw-r--r--changelogs/unreleased/empty-task-list-alignment.yml4
-rw-r--r--changelogs/unreleased/enforce-Ansi2html-output-encoding.yml4
-rw-r--r--changelogs/unreleased/fix-missing-capitalisation-buttons.yml4
-rw-r--r--changelogs/unreleased/fix-preloading-merge_request_diff.yml4
-rw-r--r--changelogs/unreleased/fix-project-visibility-setting.yml4
-rw-r--r--changelogs/unreleased/fix-trace-encoding.yml4
-rw-r--r--changelogs/unreleased/fix-trace-seeking.yml4
-rw-r--r--changelogs/unreleased/fix_cache_expiration_in_repository.yml4
-rw-r--r--changelogs/unreleased/fix_spaces_in_label_title.yml4
-rw-r--r--changelogs/unreleased/issues-empty-state-not-centered.yml4
-rw-r--r--changelogs/unreleased/move-search-labels.yml4
-rw-r--r--changelogs/unreleased/mr-widget-bug-fix.yml5
-rw-r--r--changelogs/unreleased/omnibus-gitlab-1993-check-shell-repositories-path-group-is-root.yml4
-rw-r--r--changelogs/unreleased/pms-lighter-colors.yml4
-rw-r--r--changelogs/unreleased/remove-double-newline-for-single-attachments.yml4
-rw-r--r--changelogs/unreleased/remove_is_admin.yml4
-rw-r--r--changelogs/unreleased/reset-new-branch-button.yml4
-rw-r--r--changelogs/unreleased/sh-fix-ssh-keys-with-spaces.yml4
-rw-r--r--changelogs/unreleased/siemens-gitlab-ce-fix-subgroup-hide-button.yml4
-rw-r--r--changelogs/unreleased/spec_for_schema.yml4
-rw-r--r--changelogs/unreleased/use-hashie-forbidden_attributes.yml4
-rw-r--r--changelogs/unreleased/user-activity-scroll-bar.yml4
-rw-r--r--changelogs/unreleased/zj-api-fix-build-events.yml4
-rw-r--r--changelogs/unreleased/zj-fk-ci-triggers.yml4
-rw-r--r--config/dependency_decisions.yml54
-rw-r--r--config/environments/test.rb7
-rw-r--r--config/gitlab.yml.example4
-rw-r--r--config/routes/project.rb32
-rw-r--r--config/routes/repository.rb139
-rw-r--r--config/webpack.config.js24
-rw-r--r--db/fixtures/development/17_cycle_analytics.rb6
-rw-r--r--db/fixtures/development/20_nested_groups.rb68
-rw-r--r--db/migrate/20130218141258_convert_closed_to_state_in_issue.rb2
-rw-r--r--db/migrate/20130218141327_convert_closed_to_state_in_merge_request.rb2
-rw-r--r--db/migrate/20130218141344_convert_closed_to_state_in_milestone.rb2
-rw-r--r--db/migrate/20130315124931_user_color_scheme.rb2
-rw-r--r--db/migrate/20131112220935_add_visibility_level_to_projects.rb2
-rw-r--r--db/migrate/20140313092127_migrate_already_imported_projects.rb2
-rw-r--r--db/migrate/20141007100818_add_visibility_level_to_snippet.rb2
-rw-r--r--db/migrate/20151209144329_migrate_ci_web_hooks.rb2
-rw-r--r--db/migrate/20151209145909_migrate_ci_emails.rb2
-rw-r--r--db/migrate/20151210125232_migrate_ci_slack_service.rb2
-rw-r--r--db/migrate/20151210125927_migrate_ci_hip_chat_service.rb2
-rw-r--r--db/migrate/20170407135259_add_foreigh_key_trigger_requests_trigger.rb15
-rw-r--r--db/post_migrate/20170301205640_migrate_build_events_to_pipeline_events.rb1
-rw-r--r--db/post_migrate/20170406142253_migrate_user_project_view.rb19
-rw-r--r--db/post_migrate/20170408033905_remove_old_cache_directories.rb23
-rw-r--r--db/schema.rb4
-rw-r--r--doc/README.md5
-rw-r--r--doc/administration/polling.md24
-rw-r--r--doc/api/README.md12
-rw-r--r--doc/api/access_requests.md8
-rw-r--r--doc/api/award_emoji.md18
-rw-r--r--doc/api/boards.md12
-rw-r--r--doc/api/branches.md14
-rw-r--r--doc/api/build_variables.md10
-rw-r--r--doc/api/commits.md18
-rw-r--r--doc/api/deploy_keys.md10
-rw-r--r--doc/api/deployments.md4
-rw-r--r--doc/api/enviroments.md10
-rw-r--r--doc/api/groups.md6
-rw-r--r--doc/api/issues.md30
-rw-r--r--doc/api/jobs.md22
-rw-r--r--doc/api/labels.md12
-rw-r--r--doc/api/members.md10
-rw-r--r--doc/api/merge_requests.md40
-rw-r--r--doc/api/milestones.md14
-rw-r--r--doc/api/notes.md30
-rw-r--r--doc/api/pipeline_triggers.md12
-rw-r--r--doc/api/pipelines.md12
-rw-r--r--doc/api/project_snippets.md12
-rw-r--r--doc/api/projects.md50
-rw-r--r--doc/api/repositories.md12
-rw-r--r--doc/api/runners.md6
-rw-r--r--doc/api/tags.md12
-rw-r--r--doc/ci/README.md5
-rw-r--r--doc/ci/img/pipelines.pngbin7516 -> 6298 bytes
-rw-r--r--doc/development/fe_guide/performance.md8
-rw-r--r--doc/development/fe_guide/testing.md45
-rw-r--r--doc/development/migration_style_guide.md133
-rw-r--r--doc/development/polling.md1
-rw-r--r--doc/development/testing.md429
-rw-r--r--doc/development/what_requires_downtime.md237
-rw-r--r--doc/gitlab-basics/create-group.md2
-rw-r--r--doc/gitlab-basics/img/create_new_group_info.pngbin20321 -> 105173 bytes
-rw-r--r--doc/install/README.md4
-rw-r--r--doc/install/digitaloceandocker.md5
-rw-r--r--doc/install/installation.md6
-rw-r--r--doc/topics/authentication/index.md46
-rw-r--r--doc/topics/git/index.md61
-rw-r--r--doc/topics/index.md8
-rw-r--r--doc/update/9.0-to-9.1.md4
-rw-r--r--doc/update/README.md14
-rw-r--r--doc/update/patch_versions.md2
-rw-r--r--doc/user/profile/account/two_factor_authentication.md2
-rw-r--r--doc/user/project/cycle_analytics.md4
-rw-r--r--doc/user/project/integrations/kubernetes.md2
-rw-r--r--doc/user/search/img/search_history.gifbin0 -> 265970 bytes
-rw-r--r--doc/user/search/index.md6
-rw-r--r--doc/workflow/gitlab_flow.md1
-rw-r--r--doc/workflow/groups.md4
-rw-r--r--doc/workflow/groups/new_group_form.pngbin27263 -> 114515 bytes
-rw-r--r--doc/workflow/importing/import_projects_from_github.md6
-rw-r--r--doc/workflow/shortcuts.md6
-rw-r--r--doc/workflow/todos.md2
-rw-r--r--features/steps/project/issues/award_emoji.rb6
-rw-r--r--features/steps/project/project.rb6
-rw-r--r--lib/api/entities.rb6
-rw-r--r--lib/api/groups.rb9
-rw-r--r--lib/api/helpers.rb4
-rw-r--r--lib/api/helpers/internal_helpers.rb6
-rw-r--r--lib/api/internal.rb24
-rw-r--r--lib/api/issues.rb6
-rw-r--r--lib/api/merge_requests.rb23
-rw-r--r--lib/api/notes.rb2
-rw-r--r--lib/api/project_hooks.rb12
-rw-r--r--lib/api/projects.rb36
-rw-r--r--lib/api/runners.rb8
-rw-r--r--lib/api/services.rb4
-rw-r--r--lib/api/settings.rb67
-rw-r--r--lib/api/users.rb8
-rw-r--r--lib/api/v3/groups.rb2
-rw-r--r--lib/api/v3/notes.rb2
-rw-r--r--lib/api/v3/runners.rb2
-rw-r--r--lib/api/v3/services.rb2
-rw-r--r--lib/banzai/filter/issuable_state_filter.rb4
-rw-r--r--lib/banzai/reference_parser/base_parser.rb3
-rw-r--r--lib/banzai/reference_parser/merge_request_parser.rb41
-rw-r--r--lib/bitbucket/representation/base.rb6
-rw-r--r--lib/ci/ansi2html.rb2
-rw-r--r--lib/container_registry/path.rb4
-rw-r--r--lib/container_registry/tag.rb4
-rw-r--r--lib/gitlab/bitbucket_import/importer.rb4
-rw-r--r--lib/gitlab/checks/change_access.rb5
-rw-r--r--lib/gitlab/checks/force_push.rb12
-rw-r--r--lib/gitlab/ci/trace/stream.rb22
-rw-r--r--lib/gitlab/database.rb8
-rw-r--r--lib/gitlab/database/migration_helpers.rb250
-rw-r--r--lib/gitlab/database/multi_threaded_migration.rb52
-rw-r--r--lib/gitlab/diff/diff_refs.rb6
-rw-r--r--lib/gitlab/etag_caching/middleware.rb36
-rw-r--r--lib/gitlab/etag_caching/router.rb39
-rw-r--r--lib/gitlab/git/blob.rb4
-rw-r--r--lib/gitlab/git/env.rb38
-rw-r--r--lib/gitlab/git/repository.rb80
-rw-r--r--lib/gitlab/git/rev_list.rb49
-rw-r--r--lib/gitlab/git_access.rb4
-rw-r--r--lib/gitlab/import_export/relation_factory.rb2
-rw-r--r--lib/gitlab/o_auth/user.rb2
-rw-r--r--lib/gitlab/visibility_level.rb2
-rw-r--r--lib/tasks/gitlab/check.rake14
-rw-r--r--lib/tasks/gitlab/gitaly.rake4
-rw-r--r--lib/tasks/gitlab/shell.rake7
-rw-r--r--lib/tasks/gitlab/task_helpers.rb41
-rw-r--r--lib/tasks/gitlab/update_templates.rake2
-rw-r--r--lib/tasks/gitlab/workhorse.rake4
-rw-r--r--lib/tasks/import.rake1
-rw-r--r--package.json16
-rw-r--r--qa/qa/page/main/menu.rb2
-rwxr-xr-xscripts/prepare_build.sh23
-rw-r--r--spec/controllers/projects/builds_controller_spec.rb2
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb2
-rw-r--r--spec/controllers/projects/pipelines_controller_spec.rb2
-rw-r--r--spec/factories/notes.rb3
-rw-r--r--spec/features/admin/admin_groups_spec.rb39
-rw-r--r--spec/features/admin/admin_users_spec.rb2
-rw-r--r--spec/features/dashboard/group_spec.rb14
-rw-r--r--spec/features/dashboard/shortcuts_spec.rb48
-rw-r--r--spec/features/discussion_comments/commit_spec.rb18
-rw-r--r--spec/features/discussion_comments/issue_spec.rb16
-rw-r--r--spec/features/discussion_comments/merge_request_spec.rb16
-rw-r--r--spec/features/discussion_comments/snippets_spec.rb16
-rw-r--r--spec/features/groups_spec.rb2
-rw-r--r--spec/features/issues_spec.rb4
-rw-r--r--spec/features/merge_requests/diff_notes_resolve_spec.rb2
-rw-r--r--spec/features/merge_requests/diff_notes_spec.rb238
-rw-r--r--spec/features/merge_requests/discussion_spec.rb51
-rw-r--r--spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb13
-rw-r--r--spec/features/merge_requests/user_posts_diff_notes_spec.rb294
-rw-r--r--spec/features/merge_requests/user_posts_notes.rb145
-rw-r--r--spec/features/merge_requests/user_sees_system_notes_spec.rb31
-rw-r--r--spec/features/merge_requests/versions_spec.rb (renamed from spec/features/merge_requests/merge_request_versions_spec.rb)19
-rw-r--r--spec/features/notes_on_merge_requests_spec.rb285
-rw-r--r--spec/features/projects/wiki/shortcuts_spec.rb20
-rw-r--r--spec/fixtures/trace/ansi-sequence-and-unicode5
-rw-r--r--spec/helpers/preferences_helper_spec.rb4
-rw-r--r--spec/helpers/projects_helper_spec.rb23
-rw-r--r--spec/javascripts/awards_handler_spec.js53
-rw-r--r--spec/javascripts/boards/list_spec.js40
-rw-r--r--spec/javascripts/build_spec.js137
-rw-r--r--spec/javascripts/environments/environment_spec.js5
-rw-r--r--spec/javascripts/environments/folder/environments_folder_view_spec.js5
-rw-r--r--spec/javascripts/fixtures/merge_requests.rb7
-rw-r--r--spec/javascripts/issue_show/issue_title_spec.js2
-rw-r--r--spec/javascripts/issue_spec.js194
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js13
-rw-r--r--spec/javascripts/lib/utils/number_utility_spec.js9
-rw-r--r--spec/javascripts/lib/utils/poll_spec.js93
-rw-r--r--spec/javascripts/lib/utils/text_utility_spec.js148
-rw-r--r--spec/javascripts/merged_buttons_spec.js44
-rw-r--r--spec/javascripts/user_callout_spec.js1
-rw-r--r--spec/javascripts/vue_pipelines_index/async_button_spec.js2
-rw-r--r--spec/javascripts/vue_pipelines_index/empty_state_spec.js2
-rw-r--r--spec/javascripts/vue_pipelines_index/error_state_spec.js2
-rw-r--r--spec/lib/banzai/filter/issuable_state_filter_spec.rb43
-rw-r--r--spec/lib/banzai/reference_parser/base_parser_spec.rb23
-rw-r--r--spec/lib/container_registry/path_spec.rb22
-rw-r--r--spec/lib/container_registry/tag_spec.rb7
-rw-r--r--spec/lib/gitlab/ci/trace/stream_spec.rb71
-rw-r--r--spec/lib/gitlab/ci/trace_spec.rb20
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb432
-rw-r--r--spec/lib/gitlab/database/multi_threaded_migration_spec.rb41
-rw-r--r--spec/lib/gitlab/database_spec.rb8
-rw-r--r--spec/lib/gitlab/etag_caching/router_spec.rb83
-rw-r--r--spec/lib/gitlab/git/blob_spec.rb4
-rw-r--r--spec/lib/gitlab/git/env_spec.rb102
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb107
-rw-r--r--spec/lib/gitlab/git/rev_list_spec.rb92
-rw-r--r--spec/migrations/migrate_user_project_view_spec.rb17
-rw-r--r--spec/migrations/schema_spec.rb23
-rw-r--r--spec/models/abuse_report_spec.rb3
-rw-r--r--spec/models/blob_spec.rb10
-rw-r--r--spec/models/commit_spec.rb55
-rw-r--r--spec/models/concerns/ignorable_column_spec.rb38
-rw-r--r--spec/models/container_repository_spec.rb67
-rw-r--r--spec/models/diff_note_spec.rb17
-rw-r--r--spec/models/environment_spec.rb23
-rw-r--r--spec/models/issue_spec.rb9
-rw-r--r--spec/models/label_spec.rb16
-rw-r--r--spec/models/note_spec.rb12
-rw-r--r--spec/models/project_services/chat_notification_service_spec.rb20
-rw-r--r--spec/models/repository_spec.rb49
-rw-r--r--spec/models/spam_log_spec.rb11
-rw-r--r--spec/models/user_spec.rb4
-rw-r--r--spec/policies/group_policy_spec.rb3
-rw-r--r--spec/requests/api/internal_spec.rb21
-rw-r--r--spec/requests/api/project_hooks_spec.rb13
-rw-r--r--spec/requests/api/projects_spec.rb7
-rw-r--r--spec/requests/api/session_spec.rb6
-rw-r--r--spec/routing/project_routing_spec.rb2
-rw-r--r--spec/serializers/build_serializer_spec.rb2
-rw-r--r--spec/serializers/pipeline_serializer_spec.rb2
-rw-r--r--spec/services/notification_service_spec.rb16
-rw-r--r--spec/services/projects/create_service_spec.rb14
-rw-r--r--spec/services/projects/import_service_spec.rb9
-rw-r--r--spec/services/search/global_service_spec.rb21
-rw-r--r--spec/services/search/group_service_spec.rb40
-rw-r--r--spec/services/users/build_service_spec.rb55
-rw-r--r--spec/services/users/create_service_spec.rb78
-rw-r--r--spec/services/users/destroy_service_spec.rb6
-rw-r--r--spec/spec_helper.rb14
-rw-r--r--spec/support/features/discussion_comments_shared_example.rb (renamed from spec/features/discussion_comments_spec.rb)106
-rw-r--r--spec/support/fixture_helpers.rb7
-rw-r--r--spec/support/gitaly.rb7
-rw-r--r--spec/support/test_env.rb30
-rw-r--r--spec/tasks/gitlab/gitaly_rake_spec.rb12
-rw-r--r--spec/tasks/gitlab/task_helpers_spec.rb73
-rw-r--r--spec/tasks/gitlab/workhorse_rake_spec.rb12
-rw-r--r--spec/views/projects/registry/repositories/index.html.haml_spec.rb36
-rw-r--r--vendor/assets/javascripts/notebooklab.js304
-rw-r--r--vendor/assets/javascripts/pdf.worker.js56
-rw-r--r--vendor/assets/javascripts/pdflab.js271
-rw-r--r--vendor/gitignore/C.gitignore1
-rw-r--r--vendor/gitignore/Dart.gitignore27
-rw-r--r--vendor/gitignore/Global/Eclipse.gitignore6
-rw-r--r--vendor/gitignore/Global/JetBrains.gitignore3
-rw-r--r--vendor/gitignore/Global/macOS.gitignore51
-rw-r--r--vendor/gitignore/Python.gitignore3
-rw-r--r--vendor/gitignore/Rails.gitignore2
-rw-r--r--vendor/gitignore/TeX.gitignore3
-rw-r--r--vendor/gitignore/Unity.gitignore1
-rw-r--r--vendor/gitignore/VisualStudio.gitignore3
-rw-r--r--vendor/gitlab-ci-yml/CONTRIBUTING.md5
-rw-r--r--vendor/gitlab-ci-yml/Django.gitlab-ci.yml17
-rw-r--r--vendor/gitlab-ci-yml/Pages/Hexo.gitlab-ci.yml13
-rw-r--r--vendor/gitlab-ci-yml/Pages/Jekyll.gitlab-ci.yml6
-rw-r--r--vendor/gitlab-ci-yml/Scala.gitlab-ci.yml2
-rw-r--r--vendor/licenses.csv468
-rw-r--r--yarn.lock1202
517 files changed, 10125 insertions, 5601 deletions
diff --git a/.eslintrc b/.eslintrc
index b0ae2a31919..57a08a06527 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -13,9 +13,11 @@
},
"plugins": [
"filenames",
- "import"
+ "import",
+ "html"
],
"settings": {
+ "html/html-extensions": [".html", ".html.raw", ".vue"],
"import/resolver": {
"webpack": {
"config": "./config/webpack.config.js"
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 2d3e3dcd976..d84725b202a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,4 +1,4 @@
-image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-git-2.7-phantomjs-2.1-node-7.1"
+image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.7-phantomjs-2.1-node-7.1"
cache:
key: "ruby-233"
@@ -67,6 +67,7 @@ stages:
- export CI_NODE_TOTAL=${JOB_NAME[2]}
- export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
- export KNAPSACK_GENERATE_REPORT=true
+ - export CACHE_CLASSES=true
- cp ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} ${KNAPSACK_REPORT_PATH}
- knapsack rspec "--color --format documentation"
artifacts:
@@ -87,6 +88,7 @@ stages:
- export CI_NODE_TOTAL=${JOB_NAME[2]}
- export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
- export KNAPSACK_GENERATE_REPORT=true
+ - export CACHE_CLASSES=true
- cp ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} ${KNAPSACK_REPORT_PATH}
- knapsack spinach "-r rerun" || retry '[[ -e tmp/spinach-rerun.txt ]] && bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt)'
artifacts:
@@ -349,8 +351,6 @@ migration paths:
script:
- git fetch origin v8.14.10
- git checkout -f FETCH_HEAD
- - cp config/resque.yml.example config/resque.yml
- - sed -i 's/localhost/redis/g' config/resque.yml
- bundle install --without postgres production --jobs $(nproc) $FLAGS --retry=3
- bundle exec rake db:drop db:create db:schema:load db:seed_fu
- git checkout $CI_COMMIT_SHA
diff --git a/.gitlab/issue_templates/Bug.md b/.gitlab/issue_templates/Bug.md
index 34c2e097ba8..6bb21e6a3af 100644
--- a/.gitlab/issue_templates/Bug.md
+++ b/.gitlab/issue_templates/Bug.md
@@ -25,14 +25,20 @@ logs, and code as it's very hard to read otherwise.)
#### Results of GitLab environment info
+<details>
+
(For installations with omnibus-gitlab package run and paste the output of:
`sudo gitlab-rake gitlab:env:info`)
(For installations from source run and paste the output of:
`sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`)
+</details>
+
#### Results of GitLab application Check
+<details>
+
(For installations with omnibus-gitlab package run and paste the output of:
`sudo gitlab-rake gitlab:check SANITIZE=true`)
@@ -41,6 +47,8 @@ logs, and code as it's very hard to read otherwise.)
(we will only investigate if the tests are passing)
+</details>
+
### Possible fixes
(If you can, link to the line of code that might be responsible for the problem)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 712a4970a41..4047a5b6f32 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,12 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 9.0.5 (2017-04-10)
+
+- Add shortcuts and counters to MRs and issues in navbar.
+- Disable invalid service templates.
+- Handle SSH keys that have multiple spaces between each marker.
+
## 9.0.4 (2017-04-05)
- Don’t show source project name when user does not have access.
diff --git a/Gemfile b/Gemfile
index d4b2ade4243..ad8db206da6 100644
--- a/Gemfile
+++ b/Gemfile
@@ -73,6 +73,9 @@ gem 'grape', '~> 0.19.0'
gem 'grape-entity', '~> 0.6.0'
gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
+# Disable strong_params so that Mash does not respond to :permitted?
+gem 'hashie-forbidden_attributes'
+
# Pagination
gem 'kaminari', '~> 0.17.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index d7e3f7343d0..bb91db1e805 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -346,6 +346,8 @@ GEM
tilt
hashdiff (0.3.2)
hashie (3.5.5)
+ hashie-forbidden_attributes (0.1.1)
+ hashie (>= 3.0)
health_check (2.6.0)
rails (>= 4.0)
hipchat (1.5.2)
@@ -915,6 +917,7 @@ DEPENDENCIES
grape-entity (~> 0.6.0)
haml_lint (~> 0.21.0)
hamlit (~> 2.6.1)
+ hashie-forbidden_attributes
health_check (~> 2.6.0)
hipchat (~> 1.5.0)
html-pipeline (~> 1.11.0)
@@ -1035,4 +1038,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
- 1.14.5
+ 1.14.6
diff --git a/PROCESS.md b/PROCESS.md
index cfa841dc13d..fac3c22e09f 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -57,16 +57,16 @@ star, smile, etc.). Some good tips about code reviews can be found in our
[Code Review Guidelines]: https://docs.gitlab.com/ce/development/code_review.html
-## Feature Freeze
+## Feature freeze on the 7th for the release on the 22nd
-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.
+After the 7th (Pacific Standard Time Zone) of each month, RC1 of the upcoming release (to be shipped on the 22nd) is created and deployed to GitLab.com and the stable branch for this release is frozen, which means master is no longer merged into it.
Merge requests may still be merged into master during this 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.
### Between the 1st and the 7th
-These types of merge requests need special consideration:
+These types of merge requests for the upcoming release need special consideration:
* **Large features**: a large feature is one that is highlighted in the kick-off
and the release blogpost; typically this will have its own channel in Slack
@@ -114,14 +114,15 @@ subsequent EE merge, as we often merge a lot to CE on the release date. For more
information, see
[limit conflicts with EE when developing on CE][limit_ee_conflicts].
-### Between the 7th and the 22nd
+### After the 7th
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).
+These fixes will be shipped in the next RC for that release if it is before the 22nd.
+If the fixes are are completed on or after the 22nd, they will be shipped in a patch for that release.
-If you think a merge request should go into the upcoming release even though it does not meet these requirements,
+If you think a merge request should go into an RC or patch 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
diff --git a/app/assets/images/ci_favicons/icon_status_canceled.ico b/app/assets/images/ci_favicons/favicon_status_canceled.ico
index 5a19458f2a2..5a19458f2a2 100755
--- a/app/assets/images/ci_favicons/icon_status_canceled.ico
+++ b/app/assets/images/ci_favicons/favicon_status_canceled.ico
Binary files differ
diff --git a/app/assets/images/ci_favicons/icon_status_created.ico b/app/assets/images/ci_favicons/favicon_status_created.ico
index 4dca9640cb3..4dca9640cb3 100755
--- a/app/assets/images/ci_favicons/icon_status_created.ico
+++ b/app/assets/images/ci_favicons/favicon_status_created.ico
Binary files differ
diff --git a/app/assets/images/ci_favicons/icon_status_failed.ico b/app/assets/images/ci_favicons/favicon_status_failed.ico
index c961ff9a69b..c961ff9a69b 100755
--- a/app/assets/images/ci_favicons/icon_status_failed.ico
+++ b/app/assets/images/ci_favicons/favicon_status_failed.ico
Binary files differ
diff --git a/app/assets/images/ci_favicons/icon_status_manual.ico b/app/assets/images/ci_favicons/favicon_status_manual.ico
index 5fbbc99ea7c..5fbbc99ea7c 100755
--- a/app/assets/images/ci_favicons/icon_status_manual.ico
+++ b/app/assets/images/ci_favicons/favicon_status_manual.ico
Binary files differ
diff --git a/app/assets/images/ci_favicons/icon_status_not_found.ico b/app/assets/images/ci_favicons/favicon_status_not_found.ico
index 21afa9c72e6..21afa9c72e6 100755
--- a/app/assets/images/ci_favicons/icon_status_not_found.ico
+++ b/app/assets/images/ci_favicons/favicon_status_not_found.ico
Binary files differ
diff --git a/app/assets/images/ci_favicons/icon_status_pending.ico b/app/assets/images/ci_favicons/favicon_status_pending.ico
index 8be32dab85a..8be32dab85a 100755
--- a/app/assets/images/ci_favicons/icon_status_pending.ico
+++ b/app/assets/images/ci_favicons/favicon_status_pending.ico
Binary files differ
diff --git a/app/assets/images/ci_favicons/icon_status_running.ico b/app/assets/images/ci_favicons/favicon_status_running.ico
index f328ff1a5ed..f328ff1a5ed 100755
--- a/app/assets/images/ci_favicons/icon_status_running.ico
+++ b/app/assets/images/ci_favicons/favicon_status_running.ico
Binary files differ
diff --git a/app/assets/images/ci_favicons/icon_status_skipped.ico b/app/assets/images/ci_favicons/favicon_status_skipped.ico
index b4394e1b4af..b4394e1b4af 100755
--- a/app/assets/images/ci_favicons/icon_status_skipped.ico
+++ b/app/assets/images/ci_favicons/favicon_status_skipped.ico
Binary files differ
diff --git a/app/assets/images/ci_favicons/icon_status_success.ico b/app/assets/images/ci_favicons/favicon_status_success.ico
index 4f436c95242..4f436c95242 100755
--- a/app/assets/images/ci_favicons/icon_status_success.ico
+++ b/app/assets/images/ci_favicons/favicon_status_success.ico
Binary files differ
diff --git a/app/assets/images/ci_favicons/icon_status_warning.ico b/app/assets/images/ci_favicons/favicon_status_warning.ico
index 805cc20cdec..805cc20cdec 100755
--- a/app/assets/images/ci_favicons/icon_status_warning.ico
+++ b/app/assets/images/ci_favicons/favicon_status_warning.ico
Binary files differ
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index ce426741637..f93208944a1 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -1,3 +1,5 @@
+/* global Flash */
+
import Cookies from 'js-cookie';
import emojiMap from 'emojis/digests.json';
@@ -6,6 +8,7 @@ import { glEmojiTag } from './behaviors/gl_emoji';
import isEmojiNameValid from './behaviors/gl_emoji/is_emoji_name_valid';
const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd';
+const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd';
const requestAnimationFrame = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
@@ -103,8 +106,9 @@ function AwardsHandler() {
const $glEmojiElement = $target.find('gl-emoji');
const $spriteIconElement = $target.find('.icon');
const emoji = ($glEmojiElement.length ? $glEmojiElement : $spriteIconElement).data('name');
+
$target.closest('.js-awards-block').addClass('current');
- return this.addAward(this.getVotesBlock(), this.getAwardUrl(), emoji);
+ this.addAward(this.getVotesBlock(), this.getAwardUrl(), emoji);
});
}
@@ -124,16 +128,18 @@ AwardsHandler.prototype.showEmojiMenu = function showEmojiMenu($addBtn) {
}
const $menu = $('.emoji-menu');
+ const $thumbsBtn = $menu.find('[data-name="thumbsup"], [data-name="thumbsdown"]').parent();
+ const $userAuthored = this.isUserAuthored($addBtn);
if ($menu.length) {
if ($menu.is('.is-visible')) {
$addBtn.removeClass('is-active');
$menu.removeClass('is-visible');
- $('#emoji_search').blur();
+ $('.js-emoji-menu-search').blur();
} else {
$addBtn.addClass('is-active');
this.positionMenu($menu, $addBtn);
$menu.addClass('is-visible');
- $('#emoji_search').focus();
+ $('.js-emoji-menu-search').focus();
}
} else {
$addBtn.addClass('is-loading is-active');
@@ -143,10 +149,12 @@ AwardsHandler.prototype.showEmojiMenu = function showEmojiMenu($addBtn) {
this.positionMenu($createdMenu, $addBtn);
return setTimeout(() => {
$createdMenu.addClass('is-visible');
- $('#emoji_search').focus();
+ $('.js-emoji-menu-search').focus();
}, 200);
});
}
+
+ $thumbsBtn.toggleClass('disabled', $userAuthored);
};
// Create the emoji menu with the first category of emojis.
@@ -174,7 +182,7 @@ AwardsHandler.prototype.createEmojiMenu = function createEmojiMenu(callback) {
const emojiMenuMarkup = `
<div class="emoji-menu">
- <input type="text" name="emoji_search" id="emoji_search" value="" class="emoji-search search-input form-control" placeholder="Search emoji" />
+ <input type="text" name="emoji-menu-search" value="" class="js-emoji-menu-search emoji-search search-input form-control" placeholder="Search emoji" />
<div class="emoji-menu-content">
${frequentlyUsedCatgegory}
@@ -259,7 +267,8 @@ AwardsHandler.prototype.addAward = function addAward(
callback,
) {
const normalizedEmoji = this.normalizeEmojiName(emoji);
- this.postEmoji(awardUrl, normalizedEmoji, () => {
+ const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent();
+ this.postEmoji($emojiButton, awardUrl, normalizedEmoji, () => {
this.addAwardToEmojiBar(votesBlock, normalizedEmoji, checkMutuality);
return typeof callback === 'function' ? callback() : undefined;
});
@@ -324,6 +333,10 @@ AwardsHandler.prototype.isActive = function isActive($emojiButton) {
return $emojiButton.hasClass('active');
};
+AwardsHandler.prototype.isUserAuthored = function isUserAuthored($button) {
+ return $button.hasClass('js-user-authored');
+};
+
AwardsHandler.prototype.decrementCounter = function decrementCounter($emojiButton, emoji) {
const counter = $('.js-counter', $emojiButton);
const counterNumber = parseInt(counter.text(), 10);
@@ -428,20 +441,35 @@ AwardsHandler.prototype.createEmoji = function createEmoji(votesBlock, emoji) {
});
};
-AwardsHandler.prototype.postEmoji = function postEmoji(awardUrl, emoji, callback) {
- return $.post(awardUrl, {
- name: emoji,
- }, (data) => {
- if (data.ok) {
- callback();
- }
- });
+AwardsHandler.prototype.postEmoji = function postEmoji($emojiButton, awardUrl, emoji, callback) {
+ if (this.isUserAuthored($emojiButton)) {
+ this.userAuthored($emojiButton);
+ } else {
+ $.post(awardUrl, {
+ name: emoji,
+ }, (data) => {
+ if (data.ok) {
+ callback();
+ }
+ }).fail(() => new Flash('Something went wrong on our end.'));
+ }
};
AwardsHandler.prototype.findEmojiIcon = function findEmojiIcon(votesBlock, emoji) {
return votesBlock.find(`.js-emoji-btn [data-name="${emoji}"]`);
};
+AwardsHandler.prototype.userAuthored = function userAuthored($emojiButton) {
+ const oldTitle = this.getAwardTooltip($emojiButton);
+ const newTitle = 'You cannot vote on your own issue, MR and note';
+ gl.utils.updateTooltipTitle($emojiButton, newTitle).tooltip('show');
+ // Restore tooltip back to award list
+ return setTimeout(() => {
+ $emojiButton.tooltip('hide');
+ gl.utils.updateTooltipTitle($emojiButton, oldTitle);
+ }, 2800);
+};
+
AwardsHandler.prototype.scrollToAwards = function scrollToAwards() {
const options = {
scrollTop: $('.awards').offset().top - 110,
@@ -474,24 +502,41 @@ AwardsHandler.prototype.getFrequentlyUsedEmojis = function getFrequentlyUsedEmoj
};
AwardsHandler.prototype.setupSearch = function setupSearch() {
- this.registerEventListener('on', $('input.emoji-search'), 'input', (e) => {
+ const $search = $('.js-emoji-menu-search');
+
+ this.registerEventListener('on', $search, 'input', (e) => {
const term = $(e.target).val().trim();
- // Clean previous search results
- $('ul.emoji-menu-search, h5.emoji-search-title').remove();
- if (term.length > 0) {
- // Generate a search result block
- const h5 = $('<h5 class="emoji-search-title"/>').text('Search results');
- const foundEmojis = this.searchEmojis(term).show();
- const ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(foundEmojis);
- $('.emoji-menu-content ul, .emoji-menu-content h5').hide();
- $('.emoji-menu-content').append(h5).append(ul);
- } else {
- $('.emoji-menu-content').children().show();
+ this.searchEmojis(term);
+ });
+
+ const $menu = $('.emoji-menu');
+ this.registerEventListener('on', $menu, transitionEndEventString, (e) => {
+ if (e.target === e.currentTarget) {
+ // Clear the search
+ this.searchEmojis('');
}
});
};
AwardsHandler.prototype.searchEmojis = function searchEmojis(term) {
+ const $search = $('.js-emoji-menu-search');
+ $search.val(term);
+
+ // Clean previous search results
+ $('ul.emoji-menu-search, h5.emoji-search-title').remove();
+ if (term.length > 0) {
+ // Generate a search result block
+ const h5 = $('<h5 class="emoji-search-title"/>').text('Search results');
+ const foundEmojis = this.findMatchingEmojiElements(term).show();
+ const ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(foundEmojis);
+ $('.emoji-menu-content ul, .emoji-menu-content h5').hide();
+ $('.emoji-menu-content').append(h5).append(ul);
+ } else {
+ $('.emoji-menu-content').children().show();
+ }
+};
+
+AwardsHandler.prototype.findMatchingEmojiElements = function findMatchingEmojiElements(term) {
const safeTerm = term.toLowerCase();
const namesMatchingAlias = [];
diff --git a/app/assets/javascripts/behaviors/autosize.js b/app/assets/javascripts/behaviors/autosize.js
index f7f41d55b52..3bea460dcc6 100644
--- a/app/assets/javascripts/behaviors/autosize.js
+++ b/app/assets/javascripts/behaviors/autosize.js
@@ -1,28 +1,23 @@
-/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, consistent-return, max-len */
-/* global autosize */
+import autosize from 'vendor/autosize';
-var autosize = require('vendor/autosize');
+$(() => {
+ const $fields = $('.js-autosize');
-(function() {
- $(function() {
- var $fields;
- $fields = $('.js-autosize');
- $fields.on('autosize:resized', function() {
- var $field;
- $field = $(this);
- return $field.data('height', $field.outerHeight());
- });
- $fields.on('resize.autosize', function() {
- var $field;
- $field = $(this);
- if ($field.data('height') !== $field.outerHeight()) {
- $field.data('height', $field.outerHeight());
- autosize.destroy($field);
- return $field.css('max-height', window.outerHeight);
- }
- });
- autosize($fields);
- autosize.update($fields);
- return $fields.css('resize', 'vertical');
+ $fields.on('autosize:resized', function resized() {
+ const $field = $(this);
+ $field.data('height', $field.outerHeight());
});
-}).call(window);
+
+ $fields.on('resize.autosize', function resize() {
+ const $field = $(this);
+ if ($field.data('height') !== $field.outerHeight()) {
+ $field.data('height', $field.outerHeight());
+ autosize.destroy($field);
+ $field.css('max-height', window.outerHeight);
+ }
+ });
+
+ autosize($fields);
+ autosize.update($fields);
+ $fields.css('resize', 'vertical');
+});
diff --git a/app/assets/javascripts/behaviors/details_behavior.js b/app/assets/javascripts/behaviors/details_behavior.js
index fd0840fa117..7c9dbcc8d6e 100644
--- a/app/assets/javascripts/behaviors/details_behavior.js
+++ b/app/assets/javascripts/behaviors/details_behavior.js
@@ -1,26 +1,23 @@
-/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, quotes, no-var, vars-on-top, max-len */
-(function() {
- $(function() {
- $("body").on("click", ".js-details-target", function() {
- var container;
- container = $(this).closest(".js-details-container");
- return container.toggleClass("open");
- });
- // Show details content. Hides link after click.
- //
- // %div
- // %a.js-details-expand
- // %div.js-details-content
- //
- return $("body").on("click", ".js-details-expand", function(e) {
- $(this).next('.js-details-content').removeClass("hide");
- $(this).hide();
- var truncatedItem = $(this).siblings('.js-details-short');
- if (truncatedItem.length) {
- truncatedItem.addClass("hide");
- }
- return e.preventDefault();
- });
+$(() => {
+ $('body').on('click', '.js-details-target', function target() {
+ $(this).closest('.js-details-container').toggleClass('open');
});
-}).call(window);
+
+ // Show details content. Hides link after click.
+ //
+ // %div
+ // %a.js-details-expand
+ // %div.js-details-content
+ //
+ $('body').on('click', '.js-details-expand', function expand(e) {
+ e.preventDefault();
+ $(this).next('.js-details-content').removeClass('hide');
+ $(this).hide();
+
+ const truncatedItem = $(this).siblings('.js-details-short');
+ if (truncatedItem.length) {
+ truncatedItem.addClass('hide');
+ }
+ });
+});
diff --git a/app/assets/javascripts/behaviors/index.js b/app/assets/javascripts/behaviors/index.js
new file mode 100644
index 00000000000..5b931e6cfa6
--- /dev/null
+++ b/app/assets/javascripts/behaviors/index.js
@@ -0,0 +1,9 @@
+import './autosize';
+import './bind_in_out';
+import './details_behavior';
+import { installGlEmojiElement } from './gl_emoji';
+import './quick_submit';
+import './requires_input';
+import './toggler_behavior';
+
+installGlEmojiElement();
diff --git a/app/assets/javascripts/behaviors/quick_submit.js b/app/assets/javascripts/behaviors/quick_submit.js
index 626f3503c91..3d162b24413 100644
--- a/app/assets/javascripts/behaviors/quick_submit.js
+++ b/app/assets/javascripts/behaviors/quick_submit.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, prefer-arrow-callback, camelcase, consistent-return, quotes, object-shorthand, comma-dangle, max-len */
+import '../commons/bootstrap';
// Quick Submit behavior
//
@@ -6,9 +6,6 @@
// "Meta+Enter" (Mac) or "Ctrl+Enter" (Linux/Windows) key combination, the form
// is submitted.
//
-import '../commons/bootstrap';
-
-//
// ### Example Markup
//
// <form action="/foo" class="js-quick-submit">
@@ -17,61 +14,59 @@ import '../commons/bootstrap';
// <input type="submit" value="Submit" />
// </form>
//
-(function() {
- var isMac, keyCodeIs;
- isMac = function() {
- return navigator.userAgent.match(/Macintosh/);
- };
+function isMac() {
+ return navigator.userAgent.match(/Macintosh/);
+}
- keyCodeIs = function(e, keyCode) {
- if ((e.originalEvent && e.originalEvent.repeat) || e.repeat) {
- return false;
- }
- return e.keyCode === keyCode;
- };
+function keyCodeIs(e, keyCode) {
+ if ((e.originalEvent && e.originalEvent.repeat) || e.repeat) {
+ return false;
+ }
+ return e.keyCode === keyCode;
+}
- $(document).on('keydown.quick_submit', '.js-quick-submit', function(e) {
- var $form, $submit_button;
- // Enter
- if (!keyCodeIs(e, 13)) {
- return;
- }
- if (!((e.metaKey && !e.altKey && !e.ctrlKey && !e.shiftKey) || (e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey))) {
- return;
- }
- e.preventDefault();
- $form = $(e.target).closest('form');
- $submit_button = $form.find('input[type=submit], button[type=submit]');
- if ($submit_button.attr('disabled')) {
- return;
- }
- $submit_button.disable();
- return $form.submit();
- });
+$(document).on('keydown.quick_submit', '.js-quick-submit', (e) => {
+ // Enter
+ if (!keyCodeIs(e, 13)) {
+ return;
+ }
+
+ const onlyMeta = e.metaKey && !e.altKey && !e.ctrlKey && !e.shiftKey;
+ const onlyCtrl = e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey;
+ if (!onlyMeta && !onlyCtrl) {
+ return;
+ }
+
+ e.preventDefault();
+ const $form = $(e.target).closest('form');
+ const $submitButton = $form.find('input[type=submit], button[type=submit]');
+
+ if (!$submitButton.attr('disabled')) {
+ $submitButton.disable();
+ $form.submit();
+ }
+});
+
+// If the user tabs to a submit button on a `js-quick-submit` form, display a
+// tooltip to let them know they could've used the hotkey
+$(document).on('keyup.quick_submit', '.js-quick-submit input[type=submit], .js-quick-submit button[type=submit]', function displayTooltip(e) {
+ // Tab
+ if (!keyCodeIs(e, 9)) {
+ return;
+ }
+
+ const $this = $(this);
+ const title = isMac() ?
+ 'You can also press &#8984;-Enter' :
+ 'You can also press Ctrl-Enter';
- // If the user tabs to a submit button on a `js-quick-submit` form, display a
- // tooltip to let them know they could've used the hotkey
- $(document).on('keyup.quick_submit', '.js-quick-submit input[type=submit], .js-quick-submit button[type=submit]', function(e) {
- var $this, title;
- // Tab
- if (!keyCodeIs(e, 9)) {
- return;
- }
- if (isMac()) {
- title = "You can also press &#8984;-Enter";
- } else {
- title = "You can also press Ctrl-Enter";
- }
- $this = $(this);
- return $this.tooltip({
- container: 'body',
- html: 'true',
- placement: 'auto top',
- title: title,
- trigger: 'manual'
- }).tooltip('show').one('blur', function() {
- return $this.tooltip('hide');
- });
+ $this.tooltip({
+ container: 'body',
+ html: 'true',
+ placement: 'auto top',
+ title,
+ trigger: 'manual',
});
-}).call(window);
+ $this.tooltip('show').one('blur', () => $this.tooltip('hide'));
+});
diff --git a/app/assets/javascripts/behaviors/requires_input.js b/app/assets/javascripts/behaviors/requires_input.js
index eb7143f5b1a..b20d108aa25 100644
--- a/app/assets/javascripts/behaviors/requires_input.js
+++ b/app/assets/javascripts/behaviors/requires_input.js
@@ -1,12 +1,10 @@
-/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, quotes, prefer-template, prefer-arrow-callback, no-else-return, consistent-return, max-len */
+import '../commons/bootstrap';
+
// Requires Input behavior
//
// When called on a form with input fields with the `required` attribute, the
// form's submit button will be disabled until all required fields have values.
//
-import '../commons/bootstrap';
-
-//
// ### Example Markup
//
// <form class="js-requires-input">
@@ -14,49 +12,43 @@ import '../commons/bootstrap';
// <input type="submit" value="Submit">
// </form>
//
-(function() {
- $.fn.requiresInput = function() {
- var $button, $form, fieldSelector, requireInput, required;
- $form = $(this);
- $button = $('button[type=submit], input[type=submit]', $form);
- required = '[required=required]';
- fieldSelector = "input" + required + ", select" + required + ", textarea" + required;
- requireInput = function() {
- var values;
- values = _.map($(fieldSelector, $form), function(field) {
- // Collect the input values of *all* required fields
- return field.value;
- });
- // Disable the button if any required fields are empty
- if (values.length && _.any(values, _.isEmpty)) {
- return $button.disable();
- } else {
- return $button.enable();
- }
- };
- // Set initial button state
- requireInput();
- return $form.on('change input', fieldSelector, requireInput);
- };
- $(function() {
- var $form, hideOrShowHelpBlock;
- $form = $('form.js-requires-input');
- $form.requiresInput();
- // Hide or Show the help block when creating a new project
- // based on the option selected
- hideOrShowHelpBlock = function(form) {
- var selected;
- selected = $('.js-select-namespace option:selected');
- if (selected.length && selected.data('options-parent') === 'groups') {
- return form.find('.help-block').hide();
- } else if (selected.length) {
- return form.find('.help-block').show();
- }
- };
- hideOrShowHelpBlock($form);
- return $('.select2.js-select-namespace').change(function() {
- return hideOrShowHelpBlock($form);
- });
- });
-}).call(window);
+$.fn.requiresInput = function requiresInput() {
+ const $form = $(this);
+ const $button = $('button[type=submit], input[type=submit]', $form);
+ const fieldSelector = 'input[required=required], select[required=required], textarea[required=required]';
+
+ function requireInput() {
+ // Collect the input values of *all* required fields
+ const values = _.map($(fieldSelector, $form), field => field.value);
+
+ // Disable the button if any required fields are empty
+ if (values.length && _.any(values, _.isEmpty)) {
+ $button.disable();
+ } else {
+ $button.enable();
+ }
+ }
+
+ // Set initial button state
+ requireInput();
+ $form.on('change input', fieldSelector, requireInput);
+};
+
+// Hide or Show the help block when creating a new project
+// based on the option selected
+function hideOrShowHelpBlock(form) {
+ const selected = $('.js-select-namespace option:selected');
+ if (selected.length && selected.data('options-parent') === 'groups') {
+ form.find('.help-block').hide();
+ } else if (selected.length) {
+ form.find('.help-block').show();
+ }
+}
+
+$(() => {
+ const $form = $('form.js-requires-input');
+ $form.requiresInput();
+ hideOrShowHelpBlock($form);
+ $('.select2.js-select-namespace').change(() => hideOrShowHelpBlock($form));
+});
diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js
index 576b8a0425f..77e92ff8caf 100644
--- a/app/assets/javascripts/behaviors/toggler_behavior.js
+++ b/app/assets/javascripts/behaviors/toggler_behavior.js
@@ -1,44 +1,44 @@
-/* eslint-disable wrap-iife, func-names, space-before-function-paren, prefer-arrow-callback, vars-on-top, no-var, max-len */
-(function(w) {
- $(function() {
- var toggleContainer = function(container, /* optional */toggleState) {
- var $container = $(container);
-
- $container
- .find('.js-toggle-button .fa')
- .toggleClass('fa-chevron-up', toggleState)
- .toggleClass('fa-chevron-down', toggleState !== undefined ? !toggleState : undefined);
-
- $container
- .find('.js-toggle-content')
- .toggle(toggleState);
- };
-
- // Toggle button. Show/hide content inside parent container.
- // Button does not change visibility. If button has icon - it changes chevron style.
- //
- // %div.js-toggle-container
- // %button.js-toggle-button
- // %div.js-toggle-content
- //
- $('body').on('click', '.js-toggle-button', function(e) {
- toggleContainer($(this).closest('.js-toggle-container'));
-
- const targetTag = e.currentTarget.tagName.toLowerCase();
- if (targetTag === 'a' || targetTag === 'button') {
- e.preventDefault();
- }
- });
-
- // If we're accessing a permalink, ensure it is not inside a
- // closed js-toggle-container!
- var hash = w.gl.utils.getLocationHash();
- var anchor = hash && document.getElementById(hash);
- var container = anchor && $(anchor).closest('.js-toggle-container');
-
- if (container) {
- toggleContainer(container, true);
- anchor.scrollIntoView();
+
+// Toggle button. Show/hide content inside parent container.
+// Button does not change visibility. If button has icon - it changes chevron style.
+//
+// %div.js-toggle-container
+// %button.js-toggle-button
+// %div.js-toggle-content
+//
+
+$(() => {
+ function toggleContainer(container, toggleState) {
+ const $container = $(container);
+
+ $container
+ .find('.js-toggle-button .fa')
+ .toggleClass('fa-chevron-up', toggleState)
+ .toggleClass('fa-chevron-down', toggleState !== undefined ? !toggleState : undefined);
+
+ $container
+ .find('.js-toggle-content')
+ .toggle(toggleState);
+ }
+
+ $('body').on('click', '.js-toggle-button', function toggleButton(e) {
+ e.target.classList.toggle('open');
+ toggleContainer($(this).closest('.js-toggle-container'));
+
+ const targetTag = e.currentTarget.tagName.toLowerCase();
+ if (targetTag === 'a' || targetTag === 'button') {
+ e.preventDefault();
}
});
-})(window);
+
+ // If we're accessing a permalink, ensure it is not inside a
+ // closed js-toggle-container!
+ const hash = window.gl.utils.getLocationHash();
+ const anchor = hash && document.getElementById(hash);
+ const container = anchor && $(anchor).closest('.js-toggle-container');
+
+ if (container) {
+ toggleContainer(container, true);
+ anchor.scrollIntoView();
+ }
+});
diff --git a/app/assets/javascripts/boards/boards_bundle.js b/app/assets/javascripts/boards/boards_bundle.js
index 37094c5c9be..b749ef43cd3 100644
--- a/app/assets/javascripts/boards/boards_bundle.js
+++ b/app/assets/javascripts/boards/boards_bundle.js
@@ -85,6 +85,7 @@ $(() => {
if (list.type === 'closed') {
list.position = Infinity;
+ list.label = { description: 'Shows all closed issues. Moving an issue to this list closes it' };
}
});
diff --git a/app/assets/javascripts/boards/components/board.js b/app/assets/javascripts/boards/components/board.js
index 93b8960da2e..239eeacf2d7 100644
--- a/app/assets/javascripts/boards/components/board.js
+++ b/app/assets/javascripts/boards/components/board.js
@@ -7,100 +7,98 @@ import boardBlankState from './board_blank_state';
require('./board_delete');
require('./board_list');
-(() => {
- const Store = gl.issueBoards.BoardsStore;
+const Store = gl.issueBoards.BoardsStore;
- window.gl = window.gl || {};
- window.gl.issueBoards = window.gl.issueBoards || {};
+window.gl = window.gl || {};
+window.gl.issueBoards = window.gl.issueBoards || {};
- gl.issueBoards.Board = Vue.extend({
- template: '#js-board-template',
- components: {
- boardList,
- 'board-delete': gl.issueBoards.BoardDelete,
- boardBlankState,
- },
- props: {
- list: Object,
- disabled: Boolean,
- issueLinkBase: String,
- rootPath: String,
- },
- data () {
- return {
- detailIssue: Store.detail,
- filter: Store.filter,
- };
- },
- watch: {
- filter: {
- handler() {
- this.list.page = 1;
- this.list.getIssues(true);
- },
- deep: true,
+gl.issueBoards.Board = Vue.extend({
+ template: '#js-board-template',
+ components: {
+ boardList,
+ 'board-delete': gl.issueBoards.BoardDelete,
+ boardBlankState,
+ },
+ props: {
+ list: Object,
+ disabled: Boolean,
+ issueLinkBase: String,
+ rootPath: String,
+ },
+ data () {
+ return {
+ detailIssue: Store.detail,
+ filter: Store.filter,
+ };
+ },
+ watch: {
+ filter: {
+ handler() {
+ this.list.page = 1;
+ this.list.getIssues(true);
},
- detailIssue: {
- handler () {
- if (!Object.keys(this.detailIssue.issue).length) return;
+ deep: true,
+ },
+ detailIssue: {
+ handler () {
+ if (!Object.keys(this.detailIssue.issue).length) return;
- const issue = this.list.findIssue(this.detailIssue.issue.id);
+ const issue = this.list.findIssue(this.detailIssue.issue.id);
- if (issue) {
- const offsetLeft = this.$el.offsetLeft;
- const boardsList = document.querySelectorAll('.boards-list')[0];
- const left = boardsList.scrollLeft - offsetLeft;
- let right = (offsetLeft + this.$el.offsetWidth);
+ if (issue) {
+ const offsetLeft = this.$el.offsetLeft;
+ const boardsList = document.querySelectorAll('.boards-list')[0];
+ const left = boardsList.scrollLeft - offsetLeft;
+ let right = (offsetLeft + this.$el.offsetWidth);
- if (window.innerWidth > 768 && boardsList.classList.contains('is-compact')) {
- // -290 here because width of boardsList is animating so therefore
- // getting the width here is incorrect
- // 290 is the width of the sidebar
- right -= (boardsList.offsetWidth - 290);
- } else {
- right -= boardsList.offsetWidth;
- }
+ if (window.innerWidth > 768 && boardsList.classList.contains('is-compact')) {
+ // -290 here because width of boardsList is animating so therefore
+ // getting the width here is incorrect
+ // 290 is the width of the sidebar
+ right -= (boardsList.offsetWidth - 290);
+ } else {
+ right -= boardsList.offsetWidth;
+ }
- if (right - boardsList.scrollLeft > 0) {
- $(boardsList).animate({
- scrollLeft: right
- }, this.sortableOptions.animation);
- } else if (left > 0) {
- $(boardsList).animate({
- scrollLeft: offsetLeft
- }, this.sortableOptions.animation);
- }
+ if (right - boardsList.scrollLeft > 0) {
+ $(boardsList).animate({
+ scrollLeft: right
+ }, this.sortableOptions.animation);
+ } else if (left > 0) {
+ $(boardsList).animate({
+ scrollLeft: offsetLeft
+ }, this.sortableOptions.animation);
}
- },
- deep: true
- }
- },
- methods: {
- showNewIssueForm() {
- this.$refs['board-list'].showIssueForm = !this.$refs['board-list'].showIssueForm;
- }
- },
- mounted () {
- this.sortableOptions = gl.issueBoards.getBoardSortableDefaultOptions({
- disabled: this.disabled,
- group: 'boards',
- draggable: '.is-draggable',
- handle: '.js-board-handle',
- onEnd: (e) => {
- gl.issueBoards.onEnd();
+ }
+ },
+ deep: true
+ }
+ },
+ methods: {
+ showNewIssueForm() {
+ this.$refs['board-list'].showIssueForm = !this.$refs['board-list'].showIssueForm;
+ }
+ },
+ mounted () {
+ this.sortableOptions = gl.issueBoards.getBoardSortableDefaultOptions({
+ disabled: this.disabled,
+ group: 'boards',
+ draggable: '.is-draggable',
+ handle: '.js-board-handle',
+ onEnd: (e) => {
+ gl.issueBoards.onEnd();
- if (e.newIndex !== undefined && e.oldIndex !== e.newIndex) {
- const order = this.sortable.toArray();
- const list = Store.findList('id', parseInt(e.item.dataset.id, 10));
+ if (e.newIndex !== undefined && e.oldIndex !== e.newIndex) {
+ const order = this.sortable.toArray();
+ const list = Store.findList('id', parseInt(e.item.dataset.id, 10));
- this.$nextTick(() => {
- Store.moveList(list, order);
- });
- }
+ this.$nextTick(() => {
+ Store.moveList(list, order);
+ });
}
- });
+ }
+ });
- this.sortable = Sortable.create(this.$el.parentNode, this.sortableOptions);
- },
- });
-})();
+ this.sortable = Sortable.create(this.$el.parentNode, this.sortableOptions);
+ },
+});
diff --git a/app/assets/javascripts/boards/components/board_delete.js b/app/assets/javascripts/boards/components/board_delete.js
index af621cfd57f..8a1b177bba8 100644
--- a/app/assets/javascripts/boards/components/board_delete.js
+++ b/app/assets/javascripts/boards/components/board_delete.js
@@ -2,22 +2,20 @@
import Vue from 'vue';
-(() => {
- window.gl = window.gl || {};
- window.gl.issueBoards = window.gl.issueBoards || {};
+window.gl = window.gl || {};
+window.gl.issueBoards = window.gl.issueBoards || {};
- gl.issueBoards.BoardDelete = Vue.extend({
- props: {
- list: Object
- },
- methods: {
- deleteBoard () {
- $(this.$el).tooltip('hide');
+gl.issueBoards.BoardDelete = Vue.extend({
+ props: {
+ list: Object
+ },
+ methods: {
+ deleteBoard () {
+ $(this.$el).tooltip('hide');
- if (confirm('Are you sure you want to delete this list?')) {
- this.list.destroy();
- }
+ if (confirm('Are you sure you want to delete this list?')) {
+ this.list.destroy();
}
}
- });
-})();
+ }
+});
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js
index 3c080008244..004bac09f59 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js
+++ b/app/assets/javascripts/boards/components/board_sidebar.js
@@ -8,66 +8,64 @@ import Vue from 'vue';
require('./sidebar/remove_issue');
-(() => {
- const Store = gl.issueBoards.BoardsStore;
+const Store = gl.issueBoards.BoardsStore;
- window.gl = window.gl || {};
- window.gl.issueBoards = window.gl.issueBoards || {};
+window.gl = window.gl || {};
+window.gl.issueBoards = window.gl.issueBoards || {};
- gl.issueBoards.BoardSidebar = Vue.extend({
- props: {
- currentUser: Object
- },
- data() {
- return {
- detail: Store.detail,
- issue: {},
- list: {},
- };
- },
- computed: {
- showSidebar () {
- return Object.keys(this.issue).length;
- }
- },
- watch: {
- detail: {
- handler () {
- if (this.issue.id !== this.detail.issue.id) {
- $('.js-issue-board-sidebar', this.$el).each((i, el) => {
- $(el).data('glDropdown').clearMenu();
- });
- }
-
- this.issue = this.detail.issue;
- this.list = this.detail.list;
- },
- deep: true
- },
- issue () {
- if (this.showSidebar) {
- this.$nextTick(() => {
- $('.right-sidebar').getNiceScroll(0).doScrollTop(0, 0);
- $('.right-sidebar').getNiceScroll().resize();
+gl.issueBoards.BoardSidebar = Vue.extend({
+ props: {
+ currentUser: Object
+ },
+ data() {
+ return {
+ detail: Store.detail,
+ issue: {},
+ list: {},
+ };
+ },
+ computed: {
+ showSidebar () {
+ return Object.keys(this.issue).length;
+ }
+ },
+ watch: {
+ detail: {
+ handler () {
+ if (this.issue.id !== this.detail.issue.id) {
+ $('.js-issue-board-sidebar', this.$el).each((i, el) => {
+ $(el).data('glDropdown').clearMenu();
});
}
- }
+
+ this.issue = this.detail.issue;
+ this.list = this.detail.list;
+ },
+ deep: true
},
- methods: {
- closeSidebar () {
- this.detail.issue = {};
+ issue () {
+ if (this.showSidebar) {
+ this.$nextTick(() => {
+ $('.right-sidebar').getNiceScroll(0).doScrollTop(0, 0);
+ $('.right-sidebar').getNiceScroll().resize();
+ });
}
- },
- mounted () {
- new IssuableContext(this.currentUser);
- new MilestoneSelect();
- new gl.DueDateSelectors();
- new LabelsSelect();
- new Sidebar();
- gl.Subscription.bindAll('.subscription');
- },
- components: {
- removeBtn: gl.issueBoards.RemoveIssueBtn,
- },
- });
-})();
+ }
+ },
+ methods: {
+ closeSidebar () {
+ this.detail.issue = {};
+ }
+ },
+ mounted () {
+ new IssuableContext(this.currentUser);
+ new MilestoneSelect();
+ new gl.DueDateSelectors();
+ new LabelsSelect();
+ new Sidebar();
+ gl.Subscription.bindAll('.subscription');
+ },
+ components: {
+ removeBtn: gl.issueBoards.RemoveIssueBtn,
+ },
+});
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.js b/app/assets/javascripts/boards/components/issue_card_inner.js
index e48d3344a2b..fc154ee7b8b 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner.js
+++ b/app/assets/javascripts/boards/components/issue_card_inner.js
@@ -1,141 +1,139 @@
import Vue from 'vue';
import eventHub from '../eventhub';
-(() => {
- const Store = gl.issueBoards.BoardsStore;
+const Store = gl.issueBoards.BoardsStore;
- window.gl = window.gl || {};
- window.gl.issueBoards = window.gl.issueBoards || {};
+window.gl = window.gl || {};
+window.gl.issueBoards = window.gl.issueBoards || {};
- gl.issueBoards.IssueCardInner = Vue.extend({
- props: {
- issue: {
- type: Object,
- required: true,
- },
- issueLinkBase: {
- type: String,
- required: true,
- },
- list: {
- type: Object,
- required: false,
- default: () => ({}),
- },
- rootPath: {
- type: String,
- required: true,
- },
- updateFilters: {
- type: Boolean,
- required: false,
- default: false,
- },
+gl.issueBoards.IssueCardInner = Vue.extend({
+ props: {
+ issue: {
+ type: Object,
+ required: true,
},
- computed: {
- cardUrl() {
- return `${this.issueLinkBase}/${this.issue.id}`;
- },
- assigneeUrl() {
- return `${this.rootPath}${this.issue.assignee.username}`;
- },
- assigneeUrlTitle() {
- return `Assigned to ${this.issue.assignee.name}`;
- },
- avatarUrlTitle() {
- return `Avatar for ${this.issue.assignee.name}`;
- },
- issueId() {
- return `#${this.issue.id}`;
- },
- showLabelFooter() {
- return this.issue.labels.find(l => this.showLabel(l)) !== undefined;
- },
+ issueLinkBase: {
+ type: String,
+ required: true,
},
- methods: {
- showLabel(label) {
- if (!this.list) return true;
+ list: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ rootPath: {
+ type: String,
+ required: true,
+ },
+ updateFilters: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ computed: {
+ cardUrl() {
+ return `${this.issueLinkBase}/${this.issue.id}`;
+ },
+ assigneeUrl() {
+ return `${this.rootPath}${this.issue.assignee.username}`;
+ },
+ assigneeUrlTitle() {
+ return `Assigned to ${this.issue.assignee.name}`;
+ },
+ avatarUrlTitle() {
+ return `Avatar for ${this.issue.assignee.name}`;
+ },
+ issueId() {
+ return `#${this.issue.id}`;
+ },
+ showLabelFooter() {
+ return this.issue.labels.find(l => this.showLabel(l)) !== undefined;
+ },
+ },
+ methods: {
+ showLabel(label) {
+ if (!this.list) return true;
- return !this.list.label || label.id !== this.list.label.id;
- },
- filterByLabel(label, e) {
- if (!this.updateFilters) return;
+ return !this.list.label || label.id !== this.list.label.id;
+ },
+ filterByLabel(label, e) {
+ if (!this.updateFilters) return;
- const filterPath = gl.issueBoards.BoardsStore.filter.path.split('&');
- const labelTitle = encodeURIComponent(label.title);
- const param = `label_name[]=${labelTitle}`;
- const labelIndex = filterPath.indexOf(param);
- $(e.currentTarget).tooltip('hide');
+ const filterPath = gl.issueBoards.BoardsStore.filter.path.split('&');
+ const labelTitle = encodeURIComponent(label.title);
+ const param = `label_name[]=${labelTitle}`;
+ const labelIndex = filterPath.indexOf(param);
+ $(e.currentTarget).tooltip('hide');
- if (labelIndex === -1) {
- filterPath.push(param);
- } else {
- filterPath.splice(labelIndex, 1);
- }
+ if (labelIndex === -1) {
+ filterPath.push(param);
+ } else {
+ filterPath.splice(labelIndex, 1);
+ }
- gl.issueBoards.BoardsStore.filter.path = filterPath.join('&');
+ gl.issueBoards.BoardsStore.filter.path = filterPath.join('&');
- Store.updateFiltersUrl();
+ Store.updateFiltersUrl();
- eventHub.$emit('updateTokens');
- },
- labelStyle(label) {
- return {
- backgroundColor: label.color,
- color: label.textColor,
- };
- },
+ eventHub.$emit('updateTokens');
+ },
+ labelStyle(label) {
+ return {
+ backgroundColor: label.color,
+ color: label.textColor,
+ };
},
- template: `
- <div>
- <div class="card-header">
- <h4 class="card-title">
- <i
- class="fa fa-eye-slash confidential-icon"
- v-if="issue.confidential"
- aria-hidden="true"
- />
- <a
- class="js-no-trigger"
- :href="cardUrl"
- :title="issue.title">{{ issue.title }}</a>
- <span
- class="card-number"
- v-if="issue.id"
- >
- {{ issueId }}
- </span>
- </h4>
+ },
+ template: `
+ <div>
+ <div class="card-header">
+ <h4 class="card-title">
+ <i
+ class="fa fa-eye-slash confidential-icon"
+ v-if="issue.confidential"
+ aria-hidden="true"
+ />
<a
- class="card-assignee has-tooltip js-no-trigger"
- :href="assigneeUrl"
- :title="assigneeUrlTitle"
- v-if="issue.assignee"
- data-container="body"
+ class="js-no-trigger"
+ :href="cardUrl"
+ :title="issue.title">{{ issue.title }}</a>
+ <span
+ class="card-number"
+ v-if="issue.id"
>
- <img
- class="avatar avatar-inline s20 js-no-trigger"
- :src="issue.assignee.avatar"
- width="20"
- height="20"
- :alt="avatarUrlTitle"
- />
- </a>
- </div>
- <div class="card-footer" v-if="showLabelFooter">
- <button
- class="label color-label has-tooltip js-no-trigger"
- v-for="label in issue.labels"
- type="button"
- v-if="showLabel(label)"
- @click="filterByLabel(label, $event)"
- :style="labelStyle(label)"
- :title="label.description"
- data-container="body">
- {{ label.title }}
- </button>
- </div>
+ {{ issueId }}
+ </span>
+ </h4>
+ <a
+ class="card-assignee has-tooltip js-no-trigger"
+ :href="assigneeUrl"
+ :title="assigneeUrlTitle"
+ v-if="issue.assignee"
+ data-container="body"
+ >
+ <img
+ class="avatar avatar-inline s20 js-no-trigger"
+ :src="issue.assignee.avatar"
+ width="20"
+ height="20"
+ :alt="avatarUrlTitle"
+ />
+ </a>
+ </div>
+ <div class="card-footer" v-if="showLabelFooter">
+ <button
+ class="label color-label has-tooltip js-no-trigger"
+ v-for="label in issue.labels"
+ type="button"
+ v-if="showLabel(label)"
+ @click="filterByLabel(label, $event)"
+ :style="labelStyle(label)"
+ :title="label.description"
+ data-container="body">
+ {{ label.title }}
+ </button>
</div>
- `,
- });
-})();
+ </div>
+ `,
+});
diff --git a/app/assets/javascripts/boards/components/modal/empty_state.js b/app/assets/javascripts/boards/components/modal/empty_state.js
index 823319df6e7..13569df0c20 100644
--- a/app/assets/javascripts/boards/components/modal/empty_state.js
+++ b/app/assets/javascripts/boards/components/modal/empty_state.js
@@ -1,71 +1,69 @@
import Vue from 'vue';
-(() => {
- const ModalStore = gl.issueBoards.ModalStore;
+const ModalStore = gl.issueBoards.ModalStore;
- gl.issueBoards.ModalEmptyState = Vue.extend({
- mixins: [gl.issueBoards.ModalMixins],
- data() {
- return ModalStore.store;
+gl.issueBoards.ModalEmptyState = Vue.extend({
+ mixins: [gl.issueBoards.ModalMixins],
+ data() {
+ return ModalStore.store;
+ },
+ props: {
+ image: {
+ type: String,
+ required: true,
},
- props: {
- image: {
- type: String,
- required: true,
- },
- newIssuePath: {
- type: String,
- required: true,
- },
+ newIssuePath: {
+ type: String,
+ required: true,
},
- computed: {
- contents() {
- const obj = {
- title: 'You haven\'t added any issues to your project yet',
- content: `
- An issue can be a bug, a todo or a feature request that needs to be
- discussed in a project. Besides, issues are searchable and filterable.
- `,
- };
+ },
+ computed: {
+ contents() {
+ const obj = {
+ title: 'You haven\'t added any issues to your project yet',
+ content: `
+ An issue can be a bug, a todo or a feature request that needs to be
+ discussed in a project. Besides, issues are searchable and filterable.
+ `,
+ };
- if (this.activeTab === 'selected') {
- obj.title = 'You haven\'t selected any issues yet';
- obj.content = `
- Go back to <strong>Open issues</strong> and select some issues
- to add to your board.
- `;
- }
+ if (this.activeTab === 'selected') {
+ obj.title = 'You haven\'t selected any issues yet';
+ obj.content = `
+ Go back to <strong>Open issues</strong> and select some issues
+ to add to your board.
+ `;
+ }
- return obj;
- },
+ return obj;
},
- template: `
- <section class="empty-state">
- <div class="row">
- <div class="col-xs-12 col-sm-6 col-sm-push-6">
- <aside class="svg-content" v-html="image"></aside>
- </div>
- <div class="col-xs-12 col-sm-6 col-sm-pull-6">
- <div class="text-content">
- <h4>{{ contents.title }}</h4>
- <p v-html="contents.content"></p>
- <a
- :href="newIssuePath"
- class="btn btn-success btn-inverted"
- v-if="activeTab === 'all'">
- New issue
- </a>
- <button
- type="button"
- class="btn btn-default"
- @click="changeTab('all')"
- v-if="activeTab === 'selected'">
- Open issues
- </button>
- </div>
+ },
+ template: `
+ <section class="empty-state">
+ <div class="row">
+ <div class="col-xs-12 col-sm-6 col-sm-push-6">
+ <aside class="svg-content" v-html="image"></aside>
+ </div>
+ <div class="col-xs-12 col-sm-6 col-sm-pull-6">
+ <div class="text-content">
+ <h4>{{ contents.title }}</h4>
+ <p v-html="contents.content"></p>
+ <a
+ :href="newIssuePath"
+ class="btn btn-success btn-inverted"
+ v-if="activeTab === 'all'">
+ New issue
+ </a>
+ <button
+ type="button"
+ class="btn btn-default"
+ @click="changeTab('all')"
+ v-if="activeTab === 'selected'">
+ Open issues
+ </button>
</div>
</div>
- </section>
- `,
- });
-})();
+ </div>
+ </section>
+ `,
+});
diff --git a/app/assets/javascripts/boards/components/modal/footer.js b/app/assets/javascripts/boards/components/modal/footer.js
index 887ce373096..ccd270b27da 100644
--- a/app/assets/javascripts/boards/components/modal/footer.js
+++ b/app/assets/javascripts/boards/components/modal/footer.js
@@ -5,80 +5,78 @@ import Vue from 'vue';
require('./lists_dropdown');
-(() => {
- const ModalStore = gl.issueBoards.ModalStore;
+const ModalStore = gl.issueBoards.ModalStore;
- gl.issueBoards.ModalFooter = Vue.extend({
- mixins: [gl.issueBoards.ModalMixins],
- data() {
- return {
- modal: ModalStore.store,
- state: gl.issueBoards.BoardsStore.state,
- };
+gl.issueBoards.ModalFooter = Vue.extend({
+ mixins: [gl.issueBoards.ModalMixins],
+ data() {
+ return {
+ modal: ModalStore.store,
+ state: gl.issueBoards.BoardsStore.state,
+ };
+ },
+ computed: {
+ submitDisabled() {
+ return !ModalStore.selectedCount();
},
- computed: {
- submitDisabled() {
- return !ModalStore.selectedCount();
- },
- submitText() {
- const count = ModalStore.selectedCount();
+ submitText() {
+ const count = ModalStore.selectedCount();
- return `Add ${count > 0 ? count : ''} ${gl.text.pluralize('issue', count)}`;
- },
+ return `Add ${count > 0 ? count : ''} ${gl.text.pluralize('issue', count)}`;
},
- methods: {
- addIssues() {
- const list = this.modal.selectedList || this.state.lists[0];
- const selectedIssues = ModalStore.getSelectedIssues();
- const issueIds = selectedIssues.map(issue => issue.globalId);
+ },
+ methods: {
+ addIssues() {
+ const list = this.modal.selectedList || this.state.lists[0];
+ const selectedIssues = ModalStore.getSelectedIssues();
+ const issueIds = selectedIssues.map(issue => issue.globalId);
- // Post the data to the backend
- gl.boardService.bulkUpdate(issueIds, {
- add_label_ids: [list.label.id],
- }).catch(() => {
- new Flash('Failed to update issues, please try again.', 'alert');
+ // Post the data to the backend
+ gl.boardService.bulkUpdate(issueIds, {
+ add_label_ids: [list.label.id],
+ }).catch(() => {
+ new Flash('Failed to update issues, please try again.', 'alert');
- selectedIssues.forEach((issue) => {
- list.removeIssue(issue);
- list.issuesSize -= 1;
- });
- });
-
- // Add the issues on the frontend
selectedIssues.forEach((issue) => {
- list.addIssue(issue);
- list.issuesSize += 1;
+ list.removeIssue(issue);
+ list.issuesSize -= 1;
});
+ });
- this.toggleModal(false);
- },
- },
- components: {
- 'lists-dropdown': gl.issueBoards.ModalFooterListsDropdown,
+ // Add the issues on the frontend
+ selectedIssues.forEach((issue) => {
+ list.addIssue(issue);
+ list.issuesSize += 1;
+ });
+
+ this.toggleModal(false);
},
- template: `
- <footer
- class="form-actions add-issues-footer">
- <div class="pull-left">
- <button
- class="btn btn-success"
- type="button"
- :disabled="submitDisabled"
- @click="addIssues">
- {{ submitText }}
- </button>
- <span class="inline add-issues-footer-to-list">
- to list
- </span>
- <lists-dropdown></lists-dropdown>
- </div>
+ },
+ components: {
+ 'lists-dropdown': gl.issueBoards.ModalFooterListsDropdown,
+ },
+ template: `
+ <footer
+ class="form-actions add-issues-footer">
+ <div class="pull-left">
<button
- class="btn btn-default pull-right"
+ class="btn btn-success"
type="button"
- @click="toggleModal(false)">
- Cancel
+ :disabled="submitDisabled"
+ @click="addIssues">
+ {{ submitText }}
</button>
- </footer>
- `,
- });
-})();
+ <span class="inline add-issues-footer-to-list">
+ to list
+ </span>
+ <lists-dropdown></lists-dropdown>
+ </div>
+ <button
+ class="btn btn-default pull-right"
+ type="button"
+ @click="toggleModal(false)">
+ Cancel
+ </button>
+ </footer>
+ `,
+});
diff --git a/app/assets/javascripts/boards/components/modal/header.js b/app/assets/javascripts/boards/components/modal/header.js
index 116e29cd177..e2b3f9ae7e2 100644
--- a/app/assets/javascripts/boards/components/modal/header.js
+++ b/app/assets/javascripts/boards/components/modal/header.js
@@ -3,80 +3,78 @@ import modalFilters from './filters';
require('./tabs');
-(() => {
- const ModalStore = gl.issueBoards.ModalStore;
+const ModalStore = gl.issueBoards.ModalStore;
- gl.issueBoards.ModalHeader = Vue.extend({
- mixins: [gl.issueBoards.ModalMixins],
- props: {
- projectId: {
- type: Number,
- required: true,
- },
- milestonePath: {
- type: String,
- required: true,
- },
- labelPath: {
- type: String,
- required: true,
- },
+gl.issueBoards.ModalHeader = Vue.extend({
+ mixins: [gl.issueBoards.ModalMixins],
+ props: {
+ projectId: {
+ type: Number,
+ required: true,
},
- data() {
- return ModalStore.store;
+ milestonePath: {
+ type: String,
+ required: true,
},
- computed: {
- selectAllText() {
- if (ModalStore.selectedCount() !== this.issues.length || this.issues.length === 0) {
- return 'Select all';
- }
-
- return 'Deselect all';
- },
- showSearch() {
- return this.activeTab === 'all' && !this.loading && this.issuesCount > 0;
- },
+ labelPath: {
+ type: String,
+ required: true,
},
- methods: {
- toggleAll() {
- this.$refs.selectAllBtn.blur();
+ },
+ data() {
+ return ModalStore.store;
+ },
+ computed: {
+ selectAllText() {
+ if (ModalStore.selectedCount() !== this.issues.length || this.issues.length === 0) {
+ return 'Select all';
+ }
- ModalStore.toggleAll();
- },
+ return 'Deselect all';
+ },
+ showSearch() {
+ return this.activeTab === 'all' && !this.loading && this.issuesCount > 0;
},
- components: {
- 'modal-tabs': gl.issueBoards.ModalTabs,
- modalFilters,
+ },
+ methods: {
+ toggleAll() {
+ this.$refs.selectAllBtn.blur();
+
+ ModalStore.toggleAll();
},
- template: `
- <div>
- <header class="add-issues-header form-actions">
- <h2>
- Add issues
- <button
- type="button"
- class="close"
- data-dismiss="modal"
- aria-label="Close"
- @click="toggleModal(false)">
- <span aria-hidden="true">×</span>
- </button>
- </h2>
- </header>
- <modal-tabs v-if="!loading && issuesCount > 0"></modal-tabs>
- <div
- class="add-issues-search append-bottom-10"
- v-if="showSearch">
- <modal-filters :store="filter" />
+ },
+ components: {
+ 'modal-tabs': gl.issueBoards.ModalTabs,
+ modalFilters,
+ },
+ template: `
+ <div>
+ <header class="add-issues-header form-actions">
+ <h2>
+ Add issues
<button
type="button"
- class="btn btn-success btn-inverted prepend-left-10"
- ref="selectAllBtn"
- @click="toggleAll">
- {{ selectAllText }}
+ class="close"
+ data-dismiss="modal"
+ aria-label="Close"
+ @click="toggleModal(false)">
+ <span aria-hidden="true">×</span>
</button>
- </div>
+ </h2>
+ </header>
+ <modal-tabs v-if="!loading && issuesCount > 0"></modal-tabs>
+ <div
+ class="add-issues-search append-bottom-10"
+ v-if="showSearch">
+ <modal-filters :store="filter" />
+ <button
+ type="button"
+ class="btn btn-success btn-inverted prepend-left-10"
+ ref="selectAllBtn"
+ @click="toggleAll">
+ {{ selectAllText }}
+ </button>
</div>
- `,
- });
-})();
+ </div>
+ `,
+});
diff --git a/app/assets/javascripts/boards/components/modal/index.js b/app/assets/javascripts/boards/components/modal/index.js
index 91c08cde13a..fb0aac3c0e4 100644
--- a/app/assets/javascripts/boards/components/modal/index.js
+++ b/app/assets/javascripts/boards/components/modal/index.js
@@ -8,160 +8,158 @@ require('./list');
require('./footer');
require('./empty_state');
-(() => {
- const ModalStore = gl.issueBoards.ModalStore;
+const ModalStore = gl.issueBoards.ModalStore;
- gl.issueBoards.IssuesModal = Vue.extend({
- props: {
- blankStateImage: {
- type: String,
- required: true,
- },
- newIssuePath: {
- type: String,
- required: true,
- },
- issueLinkBase: {
- type: String,
- required: true,
- },
- rootPath: {
- type: String,
- required: true,
- },
- projectId: {
- type: Number,
- required: true,
- },
- milestonePath: {
- type: String,
- required: true,
- },
- labelPath: {
- type: String,
- required: true,
- },
+gl.issueBoards.IssuesModal = Vue.extend({
+ props: {
+ blankStateImage: {
+ type: String,
+ required: true,
},
- data() {
- return ModalStore.store;
+ newIssuePath: {
+ type: String,
+ required: true,
},
- watch: {
- page() {
- this.loadIssues();
- },
- showAddIssuesModal() {
- if (this.showAddIssuesModal && !this.issues.length) {
- this.loading = true;
+ issueLinkBase: {
+ type: String,
+ required: true,
+ },
+ rootPath: {
+ type: String,
+ required: true,
+ },
+ projectId: {
+ type: Number,
+ required: true,
+ },
+ milestonePath: {
+ type: String,
+ required: true,
+ },
+ labelPath: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return ModalStore.store;
+ },
+ watch: {
+ page() {
+ this.loadIssues();
+ },
+ showAddIssuesModal() {
+ if (this.showAddIssuesModal && !this.issues.length) {
+ this.loading = true;
+
+ this.loadIssues()
+ .then(() => {
+ this.loading = false;
+ });
+ } else if (!this.showAddIssuesModal) {
+ this.issues = [];
+ this.selectedIssues = [];
+ this.issuesCount = false;
+ }
+ },
+ filter: {
+ handler() {
+ if (this.$el.tagName) {
+ this.page = 1;
+ this.filterLoading = true;
- this.loadIssues()
+ this.loadIssues(true)
.then(() => {
- this.loading = false;
+ this.filterLoading = false;
});
- } else if (!this.showAddIssuesModal) {
- this.issues = [];
- this.selectedIssues = [];
- this.issuesCount = false;
}
},
- filter: {
- handler() {
- if (this.$el.tagName) {
- this.page = 1;
- this.filterLoading = true;
-
- this.loadIssues(true)
- .then(() => {
- this.filterLoading = false;
- });
- }
- },
- deep: true,
- },
+ deep: true,
},
- methods: {
- loadIssues(clearIssues = false) {
- if (!this.showAddIssuesModal) return false;
-
- return gl.boardService.getBacklog(queryData(this.filter.path, {
- page: this.page,
- per: this.perPage,
- })).then((res) => {
- const data = res.json();
-
- if (clearIssues) {
- this.issues = [];
- }
+ },
+ methods: {
+ loadIssues(clearIssues = false) {
+ if (!this.showAddIssuesModal) return false;
- data.issues.forEach((issueObj) => {
- const issue = new ListIssue(issueObj);
- const foundSelectedIssue = ModalStore.findSelectedIssue(issue);
- issue.selected = !!foundSelectedIssue;
+ return gl.boardService.getBacklog(queryData(this.filter.path, {
+ page: this.page,
+ per: this.perPage,
+ })).then((res) => {
+ const data = res.json();
- this.issues.push(issue);
- });
+ if (clearIssues) {
+ this.issues = [];
+ }
- this.loadingNewPage = false;
+ data.issues.forEach((issueObj) => {
+ const issue = new ListIssue(issueObj);
+ const foundSelectedIssue = ModalStore.findSelectedIssue(issue);
+ issue.selected = !!foundSelectedIssue;
- if (!this.issuesCount) {
- this.issuesCount = data.size;
- }
+ this.issues.push(issue);
});
- },
- },
- computed: {
- showList() {
- if (this.activeTab === 'selected') {
- return this.selectedIssues.length > 0;
- }
- return this.issuesCount > 0;
- },
- showEmptyState() {
- if (!this.loading && this.issuesCount === 0) {
- return true;
- }
+ this.loadingNewPage = false;
- return this.activeTab === 'selected' && this.selectedIssues.length === 0;
- },
+ if (!this.issuesCount) {
+ this.issuesCount = data.size;
+ }
+ });
},
- created() {
- this.page = 1;
+ },
+ computed: {
+ showList() {
+ if (this.activeTab === 'selected') {
+ return this.selectedIssues.length > 0;
+ }
+
+ return this.issuesCount > 0;
},
- components: {
- 'modal-header': gl.issueBoards.ModalHeader,
- 'modal-list': gl.issueBoards.ModalList,
- 'modal-footer': gl.issueBoards.ModalFooter,
- 'empty-state': gl.issueBoards.ModalEmptyState,
+ showEmptyState() {
+ if (!this.loading && this.issuesCount === 0) {
+ return true;
+ }
+
+ return this.activeTab === 'selected' && this.selectedIssues.length === 0;
},
- template: `
- <div
- class="add-issues-modal"
- v-if="showAddIssuesModal">
- <div class="add-issues-container">
- <modal-header
- :project-id="projectId"
- :milestone-path="milestonePath"
- :label-path="labelPath">
- </modal-header>
- <modal-list
- :image="blankStateImage"
- :issue-link-base="issueLinkBase"
- :root-path="rootPath"
- v-if="!loading && showList && !filterLoading"></modal-list>
- <empty-state
- v-if="showEmptyState"
- :image="blankStateImage"
- :new-issue-path="newIssuePath"></empty-state>
- <section
- class="add-issues-list text-center"
- v-if="loading || filterLoading">
- <div class="add-issues-list-loading">
- <i class="fa fa-spinner fa-spin"></i>
- </div>
- </section>
- <modal-footer></modal-footer>
- </div>
+ },
+ created() {
+ this.page = 1;
+ },
+ components: {
+ 'modal-header': gl.issueBoards.ModalHeader,
+ 'modal-list': gl.issueBoards.ModalList,
+ 'modal-footer': gl.issueBoards.ModalFooter,
+ 'empty-state': gl.issueBoards.ModalEmptyState,
+ },
+ template: `
+ <div
+ class="add-issues-modal"
+ v-if="showAddIssuesModal">
+ <div class="add-issues-container">
+ <modal-header
+ :project-id="projectId"
+ :milestone-path="milestonePath"
+ :label-path="labelPath">
+ </modal-header>
+ <modal-list
+ :image="blankStateImage"
+ :issue-link-base="issueLinkBase"
+ :root-path="rootPath"
+ v-if="!loading && showList && !filterLoading"></modal-list>
+ <empty-state
+ v-if="showEmptyState"
+ :image="blankStateImage"
+ :new-issue-path="newIssuePath"></empty-state>
+ <section
+ class="add-issues-list text-center"
+ v-if="loading || filterLoading">
+ <div class="add-issues-list-loading">
+ <i class="fa fa-spinner fa-spin"></i>
+ </div>
+ </section>
+ <modal-footer></modal-footer>
</div>
- `,
- });
-})();
+ </div>
+ `,
+});
diff --git a/app/assets/javascripts/boards/components/modal/list.js b/app/assets/javascripts/boards/components/modal/list.js
index aba56d4aa31..363269c0d5d 100644
--- a/app/assets/javascripts/boards/components/modal/list.js
+++ b/app/assets/javascripts/boards/components/modal/list.js
@@ -3,159 +3,157 @@
import Vue from 'vue';
-(() => {
- const ModalStore = gl.issueBoards.ModalStore;
+const ModalStore = gl.issueBoards.ModalStore;
- gl.issueBoards.ModalList = Vue.extend({
- props: {
- issueLinkBase: {
- type: String,
- required: true,
- },
- rootPath: {
- type: String,
- required: true,
- },
- image: {
- type: String,
- required: true,
- },
+gl.issueBoards.ModalList = Vue.extend({
+ props: {
+ issueLinkBase: {
+ type: String,
+ required: true,
},
- data() {
- return ModalStore.store;
+ rootPath: {
+ type: String,
+ required: true,
},
- watch: {
- activeTab() {
- if (this.activeTab === 'all') {
- ModalStore.purgeUnselectedIssues();
- }
- },
+ image: {
+ type: String,
+ required: true,
},
- computed: {
- loopIssues() {
- if (this.activeTab === 'all') {
- return this.issues;
- }
-
- return this.selectedIssues;
- },
- groupedIssues() {
- const groups = [];
- this.loopIssues.forEach((issue, i) => {
- const index = i % this.columns;
-
- if (!groups[index]) {
- groups.push([]);
- }
-
- groups[index].push(issue);
- });
+ },
+ data() {
+ return ModalStore.store;
+ },
+ watch: {
+ activeTab() {
+ if (this.activeTab === 'all') {
+ ModalStore.purgeUnselectedIssues();
+ }
+ },
+ },
+ computed: {
+ loopIssues() {
+ if (this.activeTab === 'all') {
+ return this.issues;
+ }
- return groups;
- },
+ return this.selectedIssues;
},
- methods: {
- scrollHandler() {
- const currentPage = Math.floor(this.issues.length / this.perPage);
+ groupedIssues() {
+ const groups = [];
+ this.loopIssues.forEach((issue, i) => {
+ const index = i % this.columns;
- if ((this.scrollTop() > this.scrollHeight() - 100) && !this.loadingNewPage
- && currentPage === this.page) {
- this.loadingNewPage = true;
- this.page += 1;
+ if (!groups[index]) {
+ groups.push([]);
}
- },
- toggleIssue(e, issue) {
- if (e.target.tagName !== 'A') {
- ModalStore.toggleIssue(issue);
- }
- },
- listHeight() {
- return this.$refs.list.getBoundingClientRect().height;
- },
- scrollHeight() {
- return this.$refs.list.scrollHeight;
- },
- scrollTop() {
- return this.$refs.list.scrollTop + this.listHeight();
- },
- showIssue(issue) {
- if (this.activeTab === 'all') return true;
-
- const index = ModalStore.selectedIssueIndex(issue);
- return index !== -1;
- },
- setColumnCount() {
- const breakpoint = bp.getBreakpointSize();
+ groups[index].push(issue);
+ });
- if (breakpoint === 'lg' || breakpoint === 'md') {
- this.columns = 3;
- } else if (breakpoint === 'sm') {
- this.columns = 2;
- } else {
- this.columns = 1;
- }
- },
+ return groups;
},
- mounted() {
- this.scrollHandlerWrapper = this.scrollHandler.bind(this);
- this.setColumnCountWrapper = this.setColumnCount.bind(this);
- this.setColumnCount();
+ },
+ methods: {
+ scrollHandler() {
+ const currentPage = Math.floor(this.issues.length / this.perPage);
- this.$refs.list.addEventListener('scroll', this.scrollHandlerWrapper);
- window.addEventListener('resize', this.setColumnCountWrapper);
+ if ((this.scrollTop() > this.scrollHeight() - 100) && !this.loadingNewPage
+ && currentPage === this.page) {
+ this.loadingNewPage = true;
+ this.page += 1;
+ }
+ },
+ toggleIssue(e, issue) {
+ if (e.target.tagName !== 'A') {
+ ModalStore.toggleIssue(issue);
+ }
+ },
+ listHeight() {
+ return this.$refs.list.getBoundingClientRect().height;
},
- beforeDestroy() {
- this.$refs.list.removeEventListener('scroll', this.scrollHandlerWrapper);
- window.removeEventListener('resize', this.setColumnCountWrapper);
+ scrollHeight() {
+ return this.$refs.list.scrollHeight;
},
- components: {
- 'issue-card-inner': gl.issueBoards.IssueCardInner,
+ scrollTop() {
+ return this.$refs.list.scrollTop + this.listHeight();
},
- template: `
- <section
- class="add-issues-list add-issues-list-columns"
- ref="list">
+ showIssue(issue) {
+ if (this.activeTab === 'all') return true;
+
+ const index = ModalStore.selectedIssueIndex(issue);
+
+ return index !== -1;
+ },
+ setColumnCount() {
+ const breakpoint = bp.getBreakpointSize();
+
+ if (breakpoint === 'lg' || breakpoint === 'md') {
+ this.columns = 3;
+ } else if (breakpoint === 'sm') {
+ this.columns = 2;
+ } else {
+ this.columns = 1;
+ }
+ },
+ },
+ mounted() {
+ this.scrollHandlerWrapper = this.scrollHandler.bind(this);
+ this.setColumnCountWrapper = this.setColumnCount.bind(this);
+ this.setColumnCount();
+
+ this.$refs.list.addEventListener('scroll', this.scrollHandlerWrapper);
+ window.addEventListener('resize', this.setColumnCountWrapper);
+ },
+ beforeDestroy() {
+ this.$refs.list.removeEventListener('scroll', this.scrollHandlerWrapper);
+ window.removeEventListener('resize', this.setColumnCountWrapper);
+ },
+ components: {
+ 'issue-card-inner': gl.issueBoards.IssueCardInner,
+ },
+ template: `
+ <section
+ class="add-issues-list add-issues-list-columns"
+ ref="list">
+ <div
+ class="empty-state add-issues-empty-state-filter text-center"
+ v-if="issuesCount > 0 && issues.length === 0">
<div
- class="empty-state add-issues-empty-state-filter text-center"
- v-if="issuesCount > 0 && issues.length === 0">
- <div
- class="svg-content"
- v-html="image">
- </div>
- <div class="text-content">
- <h4>
- There are no issues to show.
- </h4>
- </div>
+ class="svg-content"
+ v-html="image">
+ </div>
+ <div class="text-content">
+ <h4>
+ There are no issues to show.
+ </h4>
</div>
+ </div>
+ <div
+ v-for="group in groupedIssues"
+ class="add-issues-list-column">
<div
- v-for="group in groupedIssues"
- class="add-issues-list-column">
+ v-for="issue in group"
+ v-if="showIssue(issue)"
+ class="card-parent">
<div
- v-for="issue in group"
- v-if="showIssue(issue)"
- class="card-parent">
- <div
- class="card"
- :class="{ 'is-active': issue.selected }"
- @click="toggleIssue($event, issue)">
- <issue-card-inner
- :issue="issue"
- :issue-link-base="issueLinkBase"
- :root-path="rootPath">
- </issue-card-inner>
- <span
- :aria-label="'Issue #' + issue.id + ' selected'"
- aria-checked="true"
- v-if="issue.selected"
- class="issue-card-selected text-center">
- <i class="fa fa-check"></i>
- </span>
- </div>
+ class="card"
+ :class="{ 'is-active': issue.selected }"
+ @click="toggleIssue($event, issue)">
+ <issue-card-inner
+ :issue="issue"
+ :issue-link-base="issueLinkBase"
+ :root-path="rootPath">
+ </issue-card-inner>
+ <span
+ :aria-label="'Issue #' + issue.id + ' selected'"
+ aria-checked="true"
+ v-if="issue.selected"
+ class="issue-card-selected text-center">
+ <i class="fa fa-check"></i>
+ </span>
</div>
</div>
- </section>
- `,
- });
-})();
+ </div>
+ </section>
+ `,
+});
diff --git a/app/assets/javascripts/boards/components/modal/lists_dropdown.js b/app/assets/javascripts/boards/components/modal/lists_dropdown.js
index 9e9ed46ab8d..8cd15df90fa 100644
--- a/app/assets/javascripts/boards/components/modal/lists_dropdown.js
+++ b/app/assets/javascripts/boards/components/modal/lists_dropdown.js
@@ -1,57 +1,55 @@
import Vue from 'vue';
-(() => {
- const ModalStore = gl.issueBoards.ModalStore;
+const ModalStore = gl.issueBoards.ModalStore;
- gl.issueBoards.ModalFooterListsDropdown = Vue.extend({
- data() {
- return {
- modal: ModalStore.store,
- state: gl.issueBoards.BoardsStore.state,
- };
+gl.issueBoards.ModalFooterListsDropdown = Vue.extend({
+ data() {
+ return {
+ modal: ModalStore.store,
+ state: gl.issueBoards.BoardsStore.state,
+ };
+ },
+ computed: {
+ selected() {
+ return this.modal.selectedList || this.state.lists[0];
},
- computed: {
- selected() {
- return this.modal.selectedList || this.state.lists[0];
- },
- },
- destroyed() {
- this.modal.selectedList = null;
- },
- template: `
- <div class="dropdown inline">
- <button
- class="dropdown-menu-toggle"
- type="button"
- data-toggle="dropdown"
- aria-expanded="false">
- <span
- class="dropdown-label-box"
- :style="{ backgroundColor: selected.label.color }">
- </span>
- {{ selected.title }}
- <i class="fa fa-chevron-down"></i>
- </button>
- <div class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up">
- <ul>
- <li
- v-for="list in state.lists"
- v-if="list.type == 'label'">
- <a
- href="#"
- role="button"
- :class="{ 'is-active': list.id == selected.id }"
- @click.prevent="modal.selectedList = list">
- <span
- class="dropdown-label-box"
- :style="{ backgroundColor: list.label.color }">
- </span>
- {{ list.title }}
- </a>
- </li>
- </ul>
- </div>
+ },
+ destroyed() {
+ this.modal.selectedList = null;
+ },
+ template: `
+ <div class="dropdown inline">
+ <button
+ class="dropdown-menu-toggle"
+ type="button"
+ data-toggle="dropdown"
+ aria-expanded="false">
+ <span
+ class="dropdown-label-box"
+ :style="{ backgroundColor: selected.label.color }">
+ </span>
+ {{ selected.title }}
+ <i class="fa fa-chevron-down"></i>
+ </button>
+ <div class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up">
+ <ul>
+ <li
+ v-for="list in state.lists"
+ v-if="list.type == 'label'">
+ <a
+ href="#"
+ role="button"
+ :class="{ 'is-active': list.id == selected.id }"
+ @click.prevent="modal.selectedList = list">
+ <span
+ class="dropdown-label-box"
+ :style="{ backgroundColor: list.label.color }">
+ </span>
+ {{ list.title }}
+ </a>
+ </li>
+ </ul>
</div>
- `,
- });
-})();
+ </div>
+ `,
+});
diff --git a/app/assets/javascripts/boards/components/modal/tabs.js b/app/assets/javascripts/boards/components/modal/tabs.js
index 23cb1b13d11..3e5d08e3d75 100644
--- a/app/assets/javascripts/boards/components/modal/tabs.js
+++ b/app/assets/javascripts/boards/components/modal/tabs.js
@@ -1,48 +1,46 @@
import Vue from 'vue';
-(() => {
- const ModalStore = gl.issueBoards.ModalStore;
+const ModalStore = gl.issueBoards.ModalStore;
- gl.issueBoards.ModalTabs = Vue.extend({
- mixins: [gl.issueBoards.ModalMixins],
- data() {
- return ModalStore.store;
+gl.issueBoards.ModalTabs = Vue.extend({
+ mixins: [gl.issueBoards.ModalMixins],
+ data() {
+ return ModalStore.store;
+ },
+ computed: {
+ selectedCount() {
+ return ModalStore.selectedCount();
},
- computed: {
- selectedCount() {
- return ModalStore.selectedCount();
- },
- },
- destroyed() {
- this.activeTab = 'all';
- },
- template: `
- <div class="top-area prepend-top-10 append-bottom-10">
- <ul class="nav-links issues-state-filters">
- <li :class="{ 'active': activeTab == 'all' }">
- <a
- href="#"
- role="button"
- @click.prevent="changeTab('all')">
- Open issues
- <span class="badge">
- {{ issuesCount }}
- </span>
- </a>
- </li>
- <li :class="{ 'active': activeTab == 'selected' }">
- <a
- href="#"
- role="button"
- @click.prevent="changeTab('selected')">
- Selected issues
- <span class="badge">
- {{ selectedCount }}
- </span>
- </a>
- </li>
- </ul>
- </div>
- `,
- });
-})();
+ },
+ destroyed() {
+ this.activeTab = 'all';
+ },
+ template: `
+ <div class="top-area prepend-top-10 append-bottom-10">
+ <ul class="nav-links issues-state-filters">
+ <li :class="{ 'active': activeTab == 'all' }">
+ <a
+ href="#"
+ role="button"
+ @click.prevent="changeTab('all')">
+ Open issues
+ <span class="badge">
+ {{ issuesCount }}
+ </span>
+ </a>
+ </li>
+ <li :class="{ 'active': activeTab == 'selected' }">
+ <a
+ href="#"
+ role="button"
+ @click.prevent="changeTab('selected')">
+ Selected issues
+ <span class="badge">
+ {{ selectedCount }}
+ </span>
+ </a>
+ </li>
+ </ul>
+ </div>
+ `,
+});
diff --git a/app/assets/javascripts/boards/components/new_list_dropdown.js b/app/assets/javascripts/boards/components/new_list_dropdown.js
index 556826a9148..22f20305624 100644
--- a/app/assets/javascripts/boards/components/new_list_dropdown.js
+++ b/app/assets/javascripts/boards/components/new_list_dropdown.js
@@ -1,76 +1,74 @@
/* eslint-disable comma-dangle, func-names, no-new, space-before-function-paren, one-var */
-(() => {
- window.gl = window.gl || {};
- window.gl.issueBoards = window.gl.issueBoards || {};
+window.gl = window.gl || {};
+window.gl.issueBoards = window.gl.issueBoards || {};
- const Store = gl.issueBoards.BoardsStore;
+const Store = gl.issueBoards.BoardsStore;
- $(document).off('created.label').on('created.label', (e, label) => {
- Store.new({
+$(document).off('created.label').on('created.label', (e, label) => {
+ Store.new({
+ title: label.title,
+ position: Store.state.lists.length - 2,
+ list_type: 'label',
+ label: {
+ id: label.id,
title: label.title,
- position: Store.state.lists.length - 2,
- list_type: 'label',
- label: {
- id: label.id,
- title: label.title,
- color: label.color
- }
- });
+ color: label.color
+ }
});
+});
- gl.issueBoards.newListDropdownInit = () => {
- $('.js-new-board-list').each(function () {
- const $this = $(this);
- new gl.CreateLabelDropdown($this.closest('.dropdown').find('.dropdown-new-label'), $this.data('namespace-path'), $this.data('project-path'));
+gl.issueBoards.newListDropdownInit = () => {
+ $('.js-new-board-list').each(function () {
+ const $this = $(this);
+ new gl.CreateLabelDropdown($this.closest('.dropdown').find('.dropdown-new-label'), $this.data('namespace-path'), $this.data('project-path'));
- $this.glDropdown({
- data(term, callback) {
- $.get($this.attr('data-labels'))
- .then((resp) => {
- callback(resp);
- });
- },
- renderRow (label) {
- const active = Store.findList('title', label.title);
- const $li = $('<li />');
- const $a = $('<a />', {
- class: (active ? `is-active js-board-list-${active.id}` : ''),
- text: label.title,
- href: '#'
- });
- const $labelColor = $('<span />', {
- class: 'dropdown-label-box',
- style: `background-color: ${label.color}`
+ $this.glDropdown({
+ data(term, callback) {
+ $.get($this.attr('data-labels'))
+ .then((resp) => {
+ callback(resp);
});
+ },
+ renderRow (label) {
+ const active = Store.findList('title', label.title);
+ const $li = $('<li />');
+ const $a = $('<a />', {
+ class: (active ? `is-active js-board-list-${active.id}` : ''),
+ text: label.title,
+ href: '#'
+ });
+ const $labelColor = $('<span />', {
+ class: 'dropdown-label-box',
+ style: `background-color: ${label.color}`
+ });
- return $li.append($a.prepend($labelColor));
- },
- search: {
- fields: ['title']
- },
- filterable: true,
- selectable: true,
- multiSelect: true,
- clicked (label, $el, e) {
- e.preventDefault();
+ return $li.append($a.prepend($labelColor));
+ },
+ search: {
+ fields: ['title']
+ },
+ filterable: true,
+ selectable: true,
+ multiSelect: true,
+ clicked (label, $el, e) {
+ e.preventDefault();
- if (!Store.findList('title', label.title)) {
- Store.new({
+ if (!Store.findList('title', label.title)) {
+ Store.new({
+ title: label.title,
+ position: Store.state.lists.length - 2,
+ list_type: 'label',
+ label: {
+ id: label.id,
title: label.title,
- position: Store.state.lists.length - 2,
- list_type: 'label',
- label: {
- id: label.id,
- title: label.title,
- color: label.color
- }
- });
+ color: label.color
+ }
+ });
- Store.state.lists = _.sortBy(Store.state.lists, 'position');
- }
+ Store.state.lists = _.sortBy(Store.state.lists, 'position');
}
- });
+ }
});
- };
-})();
+ });
+};
diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.js b/app/assets/javascripts/boards/components/sidebar/remove_issue.js
index 772ea4c5565..5597f128b80 100644
--- a/app/assets/javascripts/boards/components/sidebar/remove_issue.js
+++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.js
@@ -3,59 +3,57 @@
import Vue from 'vue';
-(() => {
- const Store = gl.issueBoards.BoardsStore;
-
- window.gl = window.gl || {};
- window.gl.issueBoards = window.gl.issueBoards || {};
-
- gl.issueBoards.RemoveIssueBtn = Vue.extend({
- props: {
- issue: {
- type: Object,
- required: true,
- },
- list: {
- type: Object,
- required: true,
- },
+const Store = gl.issueBoards.BoardsStore;
+
+window.gl = window.gl || {};
+window.gl.issueBoards = window.gl.issueBoards || {};
+
+gl.issueBoards.RemoveIssueBtn = Vue.extend({
+ props: {
+ issue: {
+ type: Object,
+ required: true,
},
- methods: {
- removeIssue() {
- const issue = this.issue;
- const lists = issue.getLists();
- const labelIds = lists.map(list => list.label.id);
-
- // Post the remove data
- gl.boardService.bulkUpdate([issue.globalId], {
- remove_label_ids: labelIds,
- }).catch(() => {
- new Flash('Failed to remove issue from board, please try again.', 'alert');
-
- lists.forEach((list) => {
- list.addIssue(issue);
- });
- });
+ list: {
+ type: Object,
+ required: true,
+ },
+ },
+ methods: {
+ removeIssue() {
+ const issue = this.issue;
+ const lists = issue.getLists();
+ const labelIds = lists.map(list => list.label.id);
+
+ // Post the remove data
+ gl.boardService.bulkUpdate([issue.globalId], {
+ remove_label_ids: labelIds,
+ }).catch(() => {
+ new Flash('Failed to remove issue from board, please try again.', 'alert');
- // Remove from the frontend store
lists.forEach((list) => {
- list.removeIssue(issue);
+ list.addIssue(issue);
});
+ });
+
+ // Remove from the frontend store
+ lists.forEach((list) => {
+ list.removeIssue(issue);
+ });
- Store.detail.issue = {};
- },
+ Store.detail.issue = {};
},
- template: `
- <div
- class="block list"
- v-if="list.type !== 'closed'">
- <button
- class="btn btn-default btn-block"
- type="button"
- @click="removeIssue">
- Remove from board
- </button>
- </div>
- `,
- });
-})();
+ },
+ template: `
+ <div
+ class="block list"
+ v-if="list.type !== 'closed'">
+ <button
+ class="btn btn-default btn-block"
+ type="button"
+ @click="removeIssue">
+ Remove from board
+ </button>
+ </div>
+ `,
+});
diff --git a/app/assets/javascripts/boards/mixins/modal_mixins.js b/app/assets/javascripts/boards/mixins/modal_mixins.js
index d378b7d4baf..2b0a1aaa89f 100644
--- a/app/assets/javascripts/boards/mixins/modal_mixins.js
+++ b/app/assets/javascripts/boards/mixins/modal_mixins.js
@@ -1,14 +1,12 @@
-(() => {
- const ModalStore = gl.issueBoards.ModalStore;
+const ModalStore = gl.issueBoards.ModalStore;
- gl.issueBoards.ModalMixins = {
- methods: {
- toggleModal(toggle) {
- ModalStore.store.showAddIssuesModal = toggle;
- },
- changeTab(tab) {
- ModalStore.store.activeTab = tab;
- },
+gl.issueBoards.ModalMixins = {
+ methods: {
+ toggleModal(toggle) {
+ ModalStore.store.showAddIssuesModal = toggle;
},
- };
-})();
+ changeTab(tab) {
+ ModalStore.store.activeTab = tab;
+ },
+ },
+};
diff --git a/app/assets/javascripts/boards/mixins/sortable_default_options.js b/app/assets/javascripts/boards/mixins/sortable_default_options.js
index b6c6d17274f..38a0eb12f92 100644
--- a/app/assets/javascripts/boards/mixins/sortable_default_options.js
+++ b/app/assets/javascripts/boards/mixins/sortable_default_options.js
@@ -1,39 +1,37 @@
/* eslint-disable no-unused-vars, no-mixed-operators, comma-dangle */
/* global DocumentTouch */
-((w) => {
- window.gl = window.gl || {};
- window.gl.issueBoards = window.gl.issueBoards || {};
+window.gl = window.gl || {};
+window.gl.issueBoards = window.gl.issueBoards || {};
- gl.issueBoards.onStart = () => {
- $('.has-tooltip').tooltip('hide')
- .tooltip('disable');
- document.body.classList.add('is-dragging');
- };
-
- gl.issueBoards.onEnd = () => {
- $('.has-tooltip').tooltip('enable');
- document.body.classList.remove('is-dragging');
- };
+gl.issueBoards.onStart = () => {
+ $('.has-tooltip').tooltip('hide')
+ .tooltip('disable');
+ document.body.classList.add('is-dragging');
+};
- gl.issueBoards.touchEnabled = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch;
+gl.issueBoards.onEnd = () => {
+ $('.has-tooltip').tooltip('enable');
+ document.body.classList.remove('is-dragging');
+};
- gl.issueBoards.getBoardSortableDefaultOptions = (obj) => {
- const defaultSortOptions = {
- animation: 200,
- forceFallback: true,
- fallbackClass: 'is-dragging',
- fallbackOnBody: true,
- ghostClass: 'is-ghost',
- filter: '.board-delete, .btn',
- delay: gl.issueBoards.touchEnabled ? 100 : 0,
- scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100,
- scrollSpeed: 20,
- onStart: gl.issueBoards.onStart,
- onEnd: gl.issueBoards.onEnd
- };
+gl.issueBoards.touchEnabled = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch;
- Object.keys(obj).forEach((key) => { defaultSortOptions[key] = obj[key]; });
- return defaultSortOptions;
+gl.issueBoards.getBoardSortableDefaultOptions = (obj) => {
+ const defaultSortOptions = {
+ animation: 200,
+ forceFallback: true,
+ fallbackClass: 'is-dragging',
+ fallbackOnBody: true,
+ ghostClass: 'is-ghost',
+ filter: '.board-delete, .btn',
+ delay: gl.issueBoards.touchEnabled ? 100 : 0,
+ scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100,
+ scrollSpeed: 20,
+ onStart: gl.issueBoards.onStart,
+ onEnd: gl.issueBoards.onEnd
};
-})(window);
+
+ Object.keys(obj).forEach((key) => { defaultSortOptions[key] = obj[key]; });
+ return defaultSortOptions;
+};
diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js
index 91e5fb2a666..f2b79a88a4a 100644
--- a/app/assets/javascripts/boards/models/list.js
+++ b/app/assets/javascripts/boards/models/list.js
@@ -3,6 +3,8 @@
/* global ListLabel */
import queryData from '../utils/query_data';
+const PER_PAGE = 20;
+
class List {
constructor (obj) {
this.id = obj.id;
@@ -58,7 +60,9 @@ class List {
nextPage () {
if (this.issuesSize > this.issues.length) {
- this.page += 1;
+ if (this.issues.length / PER_PAGE >= 1) {
+ this.page += 1;
+ }
return this.getIssues(false);
}
@@ -145,10 +149,7 @@ class List {
}
updateIssueLabel(issue, listFrom, moveBeforeIid, moveAfterIid) {
- gl.boardService.moveIssue(issue.id, listFrom.id, this.id, moveBeforeIid, moveAfterIid)
- .then(() => {
- listFrom.getIssues(false);
- });
+ gl.boardService.moveIssue(issue.id, listFrom.id, this.id, moveBeforeIid, moveAfterIid);
}
findIssue (id) {
diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js
index bcda70d0638..66384d9c038 100644
--- a/app/assets/javascripts/boards/stores/boards_store.js
+++ b/app/assets/javascripts/boards/stores/boards_store.js
@@ -3,125 +3,123 @@
import Cookies from 'js-cookie';
-(() => {
- window.gl = window.gl || {};
- window.gl.issueBoards = window.gl.issueBoards || {};
+window.gl = window.gl || {};
+window.gl.issueBoards = window.gl.issueBoards || {};
- gl.issueBoards.BoardsStore = {
- disabled: false,
- filter: {
- path: '',
- },
- state: {},
- detail: {
- issue: {}
- },
- moving: {
- issue: {},
- list: {}
- },
- create () {
- this.state.lists = [];
- this.filter.path = gl.utils.getUrlParamsArray().join('&');
- },
- addList (listObj) {
- const list = new List(listObj);
- this.state.lists.push(list);
+gl.issueBoards.BoardsStore = {
+ disabled: false,
+ filter: {
+ path: '',
+ },
+ state: {},
+ detail: {
+ issue: {}
+ },
+ moving: {
+ issue: {},
+ list: {}
+ },
+ create () {
+ this.state.lists = [];
+ this.filter.path = gl.utils.getUrlParamsArray().join('&');
+ },
+ addList (listObj) {
+ const list = new List(listObj);
+ this.state.lists.push(list);
- return list;
- },
- new (listObj) {
- const list = this.addList(listObj);
+ return list;
+ },
+ new (listObj) {
+ const list = this.addList(listObj);
- list
- .save()
- .then(() => {
- this.state.lists = _.sortBy(this.state.lists, 'position');
- });
- this.removeBlankState();
- },
- updateNewListDropdown (listId) {
- $(`.js-board-list-${listId}`).removeClass('is-active');
- },
- shouldAddBlankState () {
- // Decide whether to add the blank state
- return !(this.state.lists.filter(list => list.type !== 'closed')[0]);
- },
- addBlankState () {
- if (!this.shouldAddBlankState() || this.welcomeIsHidden() || this.disabled) return;
-
- this.addList({
- id: 'blank',
- list_type: 'blank',
- title: 'Welcome to your Issue Board!',
- position: 0
+ list
+ .save()
+ .then(() => {
+ this.state.lists = _.sortBy(this.state.lists, 'position');
});
+ this.removeBlankState();
+ },
+ updateNewListDropdown (listId) {
+ $(`.js-board-list-${listId}`).removeClass('is-active');
+ },
+ shouldAddBlankState () {
+ // Decide whether to add the blank state
+ return !(this.state.lists.filter(list => list.type !== 'closed')[0]);
+ },
+ addBlankState () {
+ if (!this.shouldAddBlankState() || this.welcomeIsHidden() || this.disabled) return;
- this.state.lists = _.sortBy(this.state.lists, 'position');
- },
- removeBlankState () {
- this.removeList('blank');
-
- Cookies.set('issue_board_welcome_hidden', 'true', {
- expires: 365 * 10,
- path: ''
- });
- },
- welcomeIsHidden () {
- return Cookies.get('issue_board_welcome_hidden') === 'true';
- },
- removeList (id, type = 'blank') {
- const list = this.findList('id', id, type);
+ this.addList({
+ id: 'blank',
+ list_type: 'blank',
+ title: 'Welcome to your Issue Board!',
+ position: 0
+ });
- if (!list) return;
+ this.state.lists = _.sortBy(this.state.lists, 'position');
+ },
+ removeBlankState () {
+ this.removeList('blank');
- this.state.lists = this.state.lists.filter(list => list.id !== id);
- },
- moveList (listFrom, orderLists) {
- orderLists.forEach((id, i) => {
- const list = this.findList('id', parseInt(id, 10));
+ Cookies.set('issue_board_welcome_hidden', 'true', {
+ expires: 365 * 10,
+ path: ''
+ });
+ },
+ welcomeIsHidden () {
+ return Cookies.get('issue_board_welcome_hidden') === 'true';
+ },
+ removeList (id, type = 'blank') {
+ const list = this.findList('id', id, type);
- list.position = i;
- });
- listFrom.update();
- },
- moveIssueToList (listFrom, listTo, issue, newIndex) {
- const issueTo = listTo.findIssue(issue.id);
- const issueLists = issue.getLists();
- const listLabels = issueLists.map(listIssue => listIssue.label);
+ if (!list) return;
- if (!issueTo) {
- // Add to new lists issues if it doesn't already exist
- listTo.addIssue(issue, listFrom, newIndex);
- } else {
- listTo.updateIssueLabel(issue, listFrom);
- issueTo.removeLabel(listFrom.label);
- }
+ this.state.lists = this.state.lists.filter(list => list.id !== id);
+ },
+ moveList (listFrom, orderLists) {
+ orderLists.forEach((id, i) => {
+ const list = this.findList('id', parseInt(id, 10));
- if (listTo.type === 'closed') {
- issueLists.forEach((list) => {
- list.removeIssue(issue);
- });
- issue.removeLabels(listLabels);
- } else {
- listFrom.removeIssue(issue);
- }
- },
- moveIssueInList (list, issue, oldIndex, newIndex, idArray) {
- const beforeId = parseInt(idArray[newIndex - 1], 10) || null;
- const afterId = parseInt(idArray[newIndex + 1], 10) || null;
+ list.position = i;
+ });
+ listFrom.update();
+ },
+ moveIssueToList (listFrom, listTo, issue, newIndex) {
+ const issueTo = listTo.findIssue(issue.id);
+ const issueLists = issue.getLists();
+ const listLabels = issueLists.map(listIssue => listIssue.label);
- list.moveIssue(issue, oldIndex, newIndex, beforeId, afterId);
- },
- findList (key, val, type = 'label') {
- return this.state.lists.filter((list) => {
- const byType = type ? list['type'] === type : true;
+ if (!issueTo) {
+ // Add to new lists issues if it doesn't already exist
+ listTo.addIssue(issue, listFrom, newIndex);
+ } else {
+ listTo.updateIssueLabel(issue, listFrom);
+ issueTo.removeLabel(listFrom.label);
+ }
- return list[key] === val && byType;
- })[0];
- },
- updateFiltersUrl () {
- history.pushState(null, null, `?${this.filter.path}`);
+ if (listTo.type === 'closed') {
+ issueLists.forEach((list) => {
+ list.removeIssue(issue);
+ });
+ issue.removeLabels(listLabels);
+ } else {
+ listFrom.removeIssue(issue);
}
- };
-})();
+ },
+ moveIssueInList (list, issue, oldIndex, newIndex, idArray) {
+ const beforeId = parseInt(idArray[newIndex - 1], 10) || null;
+ const afterId = parseInt(idArray[newIndex + 1], 10) || null;
+
+ list.moveIssue(issue, oldIndex, newIndex, beforeId, afterId);
+ },
+ findList (key, val, type = 'label') {
+ return this.state.lists.filter((list) => {
+ const byType = type ? list['type'] === type : true;
+
+ return list[key] === val && byType;
+ })[0];
+ },
+ updateFiltersUrl () {
+ history.pushState(null, null, `?${this.filter.path}`);
+ }
+};
diff --git a/app/assets/javascripts/boards/stores/modal_store.js b/app/assets/javascripts/boards/stores/modal_store.js
index 9b009483a3c..4fdc925c825 100644
--- a/app/assets/javascripts/boards/stores/modal_store.js
+++ b/app/assets/javascripts/boards/stores/modal_store.js
@@ -1,100 +1,98 @@
-(() => {
- window.gl = window.gl || {};
- window.gl.issueBoards = window.gl.issueBoards || {};
-
- class ModalStore {
- constructor() {
- this.store = {
- columns: 3,
- issues: [],
- issuesCount: false,
- selectedIssues: [],
- showAddIssuesModal: false,
- activeTab: 'all',
- selectedList: null,
- searchTerm: '',
- loading: false,
- loadingNewPage: false,
- filterLoading: false,
- page: 1,
- perPage: 50,
- filter: {
- path: '',
- },
- };
- }
+window.gl = window.gl || {};
+window.gl.issueBoards = window.gl.issueBoards || {};
+
+class ModalStore {
+ constructor() {
+ this.store = {
+ columns: 3,
+ issues: [],
+ issuesCount: false,
+ selectedIssues: [],
+ showAddIssuesModal: false,
+ activeTab: 'all',
+ selectedList: null,
+ searchTerm: '',
+ loading: false,
+ loadingNewPage: false,
+ filterLoading: false,
+ page: 1,
+ perPage: 50,
+ filter: {
+ path: '',
+ },
+ };
+ }
- selectedCount() {
- return this.getSelectedIssues().length;
- }
+ selectedCount() {
+ return this.getSelectedIssues().length;
+ }
- toggleIssue(issueObj) {
- const issue = issueObj;
- const selected = issue.selected;
+ toggleIssue(issueObj) {
+ const issue = issueObj;
+ const selected = issue.selected;
- issue.selected = !selected;
+ issue.selected = !selected;
- if (!selected) {
- this.addSelectedIssue(issue);
- } else {
- this.removeSelectedIssue(issue);
- }
+ if (!selected) {
+ this.addSelectedIssue(issue);
+ } else {
+ this.removeSelectedIssue(issue);
}
+ }
- toggleAll() {
- const select = this.selectedCount() !== this.store.issues.length;
+ toggleAll() {
+ const select = this.selectedCount() !== this.store.issues.length;
- this.store.issues.forEach((issue) => {
- const issueUpdate = issue;
+ this.store.issues.forEach((issue) => {
+ const issueUpdate = issue;
- if (issueUpdate.selected !== select) {
- issueUpdate.selected = select;
+ if (issueUpdate.selected !== select) {
+ issueUpdate.selected = select;
- if (select) {
- this.addSelectedIssue(issue);
- } else {
- this.removeSelectedIssue(issue);
- }
+ if (select) {
+ this.addSelectedIssue(issue);
+ } else {
+ this.removeSelectedIssue(issue);
}
- });
- }
+ }
+ });
+ }
- getSelectedIssues() {
- return this.store.selectedIssues.filter(issue => issue.selected);
- }
+ getSelectedIssues() {
+ return this.store.selectedIssues.filter(issue => issue.selected);
+ }
- addSelectedIssue(issue) {
- const index = this.selectedIssueIndex(issue);
+ addSelectedIssue(issue) {
+ const index = this.selectedIssueIndex(issue);
- if (index === -1) {
- this.store.selectedIssues.push(issue);
- }
+ if (index === -1) {
+ this.store.selectedIssues.push(issue);
}
+ }
- removeSelectedIssue(issue, forcePurge = false) {
- if (this.store.activeTab === 'all' || forcePurge) {
- this.store.selectedIssues = this.store.selectedIssues
- .filter(fIssue => fIssue.id !== issue.id);
- }
+ removeSelectedIssue(issue, forcePurge = false) {
+ if (this.store.activeTab === 'all' || forcePurge) {
+ this.store.selectedIssues = this.store.selectedIssues
+ .filter(fIssue => fIssue.id !== issue.id);
}
+ }
- purgeUnselectedIssues() {
- this.store.selectedIssues.forEach((issue) => {
- if (!issue.selected) {
- this.removeSelectedIssue(issue, true);
- }
- });
- }
+ purgeUnselectedIssues() {
+ this.store.selectedIssues.forEach((issue) => {
+ if (!issue.selected) {
+ this.removeSelectedIssue(issue, true);
+ }
+ });
+ }
- selectedIssueIndex(issue) {
- return this.store.selectedIssues.indexOf(issue);
- }
+ selectedIssueIndex(issue) {
+ return this.store.selectedIssues.indexOf(issue);
+ }
- findSelectedIssue(issue) {
- return this.store.selectedIssues
- .filter(filteredIssue => filteredIssue.id === issue.id)[0];
- }
+ findSelectedIssue(issue) {
+ return this.store.selectedIssues
+ .filter(filteredIssue => filteredIssue.id === issue.id)[0];
}
+}
- gl.issueBoards.ModalStore = new ModalStore();
-})();
+gl.issueBoards.ModalStore = new ModalStore();
diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js
index 9e7df9ad350..97f279e4be4 100644
--- a/app/assets/javascripts/build.js
+++ b/app/assets/javascripts/build.js
@@ -2,6 +2,8 @@
consistent-return, prefer-rest-params */
/* global Breakpoints */
+import { bytesToKiB } from './lib/utils/number_utils';
+
const bind = function (fn, me) { return function () { return fn.apply(me, arguments); }; };
const AUTO_SCROLL_OFFSET = 75;
const DOWN_BUILD_TRACE = '#down-build-trace';
@@ -20,6 +22,7 @@ window.Build = (function () {
this.state = this.options.logState;
this.buildStage = this.options.buildStage;
this.$document = $(document);
+ this.logBytes = 0;
this.updateDropdown = bind(this.updateDropdown, this);
@@ -90,21 +93,30 @@ window.Build = (function () {
success: ((log) => {
const $buildContainer = $('.js-build-output');
+ gl.utils.setCiStatusFavicon(`${this.pageUrl}/status.json`);
+
if (log.state) {
this.state = log.state;
}
if (log.append) {
$buildContainer.append(log.html);
+ this.logBytes += log.size;
} else {
$buildContainer.html(log.html);
- if (log.truncated) {
- $('.js-truncated-info-size').html(` ${log.size} `);
- this.$truncatedInfo.removeClass('hidden');
- this.initAffixTruncatedInfo();
- } else {
- this.$truncatedInfo.addClass('hidden');
- }
+ this.logBytes = log.size;
+ }
+
+ // if the incremental sum of logBytes we received is less than the total
+ // we need to show a message warning the user about that.
+ if (this.logBytes < log.total) {
+ // size is in bytes, we need to calculate KiB
+ const size = bytesToKiB(this.logBytes);
+ $('.js-truncated-info-size').html(`${size}`);
+ this.$truncatedInfo.removeClass('hidden');
+ this.initAffixTruncatedInfo();
+ } else {
+ this.$truncatedInfo.addClass('hidden');
}
this.checkAutoscroll();
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.js b/app/assets/javascripts/commit/pipelines/pipelines_table.js
index da9707549f9..1d16c64e07e 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_table.js
+++ b/app/assets/javascripts/commit/pipelines/pipelines_table.js
@@ -4,8 +4,8 @@ import PipelinesTableComponent from '../../vue_shared/components/pipelines_table
import PipelinesService from '../../vue_pipelines_index/services/pipelines_service';
import PipelineStore from '../../vue_pipelines_index/stores/pipelines_store';
import eventHub from '../../vue_pipelines_index/event_hub';
-import EmptyState from '../../vue_pipelines_index/components/empty_state';
-import ErrorState from '../../vue_pipelines_index/components/error_state';
+import EmptyState from '../../vue_pipelines_index/components/empty_state.vue';
+import ErrorState from '../../vue_pipelines_index/components/error_state.vue';
import '../../lib/utils/common_utils';
import '../../vue_shared/vue_resource_interceptor';
import Poll from '../../lib/utils/poll';
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_code_component.js b/app/assets/javascripts/cycle_analytics/components/stage_code_component.js
index 3f419a96ff9..80bd2df6f42 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_code_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/stage_code_component.js
@@ -2,46 +2,45 @@
import Vue from 'vue';
-((global) => {
- global.cycleAnalytics = global.cycleAnalytics || {};
+const global = window.gl || (window.gl = {});
+global.cycleAnalytics = global.cycleAnalytics || {};
- global.cycleAnalytics.StageCodeComponent = Vue.extend({
- props: {
- items: Array,
- stage: Object,
- },
- template: `
- <div>
- <div class="events-description">
- {{ stage.description }}
- <limit-warning :count="items.length" />
- </div>
- <ul class="stage-event-list">
- <li v-for="mergeRequest in items" class="stage-event-item">
- <div class="item-details">
- <img class="avatar" :src="mergeRequest.author.avatarUrl">
- <h5 class="item-title merge-merquest-title">
- <a :href="mergeRequest.url">
- {{ mergeRequest.title }}
- </a>
- </h5>
- <a :href="mergeRequest.url" class="issue-link">!{{ mergeRequest.iid }}</a>
- &middot;
- <span>
- Opened
- <a :href="mergeRequest.url" class="issue-date">{{ mergeRequest.createdAt }}</a>
- </span>
- <span>
- by
- <a :href="mergeRequest.author.webUrl" class="issue-author-link">{{ mergeRequest.author.name }}</a>
- </span>
- </div>
- <div class="item-time">
- <total-time :time="mergeRequest.totalTime"></total-time>
- </div>
- </li>
- </ul>
+global.cycleAnalytics.StageCodeComponent = Vue.extend({
+ props: {
+ items: Array,
+ stage: Object,
+ },
+ template: `
+ <div>
+ <div class="events-description">
+ {{ stage.description }}
+ <limit-warning :count="items.length" />
</div>
- `,
- });
-})(window.gl || (window.gl = {}));
+ <ul class="stage-event-list">
+ <li v-for="mergeRequest in items" class="stage-event-item">
+ <div class="item-details">
+ <img class="avatar" :src="mergeRequest.author.avatarUrl">
+ <h5 class="item-title merge-merquest-title">
+ <a :href="mergeRequest.url">
+ {{ mergeRequest.title }}
+ </a>
+ </h5>
+ <a :href="mergeRequest.url" class="issue-link">!{{ mergeRequest.iid }}</a>
+ &middot;
+ <span>
+ Opened
+ <a :href="mergeRequest.url" class="issue-date">{{ mergeRequest.createdAt }}</a>
+ </span>
+ <span>
+ by
+ <a :href="mergeRequest.author.webUrl" class="issue-author-link">{{ mergeRequest.author.name }}</a>
+ </span>
+ </div>
+ <div class="item-time">
+ <total-time :time="mergeRequest.totalTime"></total-time>
+ </div>
+ </li>
+ </ul>
+ </div>
+ `,
+});
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_issue_component.js b/app/assets/javascripts/cycle_analytics/components/stage_issue_component.js
index 7ffa38edd9e..20a43798fbe 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_issue_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/stage_issue_component.js
@@ -2,48 +2,47 @@
import Vue from 'vue';
-((global) => {
- global.cycleAnalytics = global.cycleAnalytics || {};
+const global = window.gl || (window.gl = {});
+global.cycleAnalytics = global.cycleAnalytics || {};
- global.cycleAnalytics.StageIssueComponent = Vue.extend({
- props: {
- items: Array,
- stage: Object,
- },
- template: `
- <div>
- <div class="events-description">
- {{ stage.description }}
- <limit-warning :count="items.length" />
- </div>
- <ul class="stage-event-list">
- <li v-for="issue in items" class="stage-event-item">
- <div class="item-details">
- <img class="avatar" :src="issue.author.avatarUrl">
- <h5 class="item-title issue-title">
- <a class="issue-title" :href="issue.url">
- {{ issue.title }}
- </a>
- </h5>
- <a :href="issue.url" class="issue-link">#{{ issue.iid }}</a>
- &middot;
- <span>
- Opened
- <a :href="issue.url" class="issue-date">{{ issue.createdAt }}</a>
- </span>
- <span>
- by
- <a :href="issue.author.webUrl" class="issue-author-link">
- {{ issue.author.name }}
- </a>
- </span>
- </div>
- <div class="item-time">
- <total-time :time="issue.totalTime"></total-time>
- </div>
- </li>
- </ul>
+global.cycleAnalytics.StageIssueComponent = Vue.extend({
+ props: {
+ items: Array,
+ stage: Object,
+ },
+ template: `
+ <div>
+ <div class="events-description">
+ {{ stage.description }}
+ <limit-warning :count="items.length" />
</div>
- `,
- });
-})(window.gl || (window.gl = {}));
+ <ul class="stage-event-list">
+ <li v-for="issue in items" class="stage-event-item">
+ <div class="item-details">
+ <img class="avatar" :src="issue.author.avatarUrl">
+ <h5 class="item-title issue-title">
+ <a class="issue-title" :href="issue.url">
+ {{ issue.title }}
+ </a>
+ </h5>
+ <a :href="issue.url" class="issue-link">#{{ issue.iid }}</a>
+ &middot;
+ <span>
+ Opened
+ <a :href="issue.url" class="issue-date">{{ issue.createdAt }}</a>
+ </span>
+ <span>
+ by
+ <a :href="issue.author.webUrl" class="issue-author-link">
+ {{ issue.author.name }}
+ </a>
+ </span>
+ </div>
+ <div class="item-time">
+ <total-time :time="issue.totalTime"></total-time>
+ </div>
+ </li>
+ </ul>
+ </div>
+ `,
+});
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js
index d736c8b0c28..f33cac3da82 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js
@@ -2,50 +2,49 @@
import Vue from 'vue';
import iconCommit from '../svg/icon_commit.svg';
-((global) => {
- global.cycleAnalytics = global.cycleAnalytics || {};
+const global = window.gl || (window.gl = {});
+global.cycleAnalytics = global.cycleAnalytics || {};
- global.cycleAnalytics.StagePlanComponent = Vue.extend({
- props: {
- items: Array,
- stage: Object,
- },
+global.cycleAnalytics.StagePlanComponent = Vue.extend({
+ props: {
+ items: Array,
+ stage: Object,
+ },
- data() {
- return { iconCommit };
- },
+ data() {
+ return { iconCommit };
+ },
- template: `
- <div>
- <div class="events-description">
- {{ stage.description }}
- <limit-warning :count="items.length" />
- </div>
- <ul class="stage-event-list">
- <li v-for="commit in items" class="stage-event-item">
- <div class="item-details item-conmmit-component">
- <img class="avatar" :src="commit.author.avatarUrl">
- <h5 class="item-title commit-title">
- <a :href="commit.commitUrl">
- {{ commit.title }}
- </a>
- </h5>
- <span>
- First
- <span class="commit-icon">${iconCommit}</span>
- <a :href="commit.commitUrl" class="commit-hash-link monospace">{{ commit.shortSha }}</a>
- pushed by
- <a :href="commit.author.webUrl" class="commit-author-link">
- {{ commit.author.name }}
- </a>
- </span>
- </div>
- <div class="item-time">
- <total-time :time="commit.totalTime"></total-time>
- </div>
- </li>
- </ul>
+ template: `
+ <div>
+ <div class="events-description">
+ {{ stage.description }}
+ <limit-warning :count="items.length" />
</div>
- `,
- });
-})(window.gl || (window.gl = {}));
+ <ul class="stage-event-list">
+ <li v-for="commit in items" class="stage-event-item">
+ <div class="item-details item-conmmit-component">
+ <img class="avatar" :src="commit.author.avatarUrl">
+ <h5 class="item-title commit-title">
+ <a :href="commit.commitUrl">
+ {{ commit.title }}
+ </a>
+ </h5>
+ <span>
+ First
+ <span class="commit-icon">${iconCommit}</span>
+ <a :href="commit.commitUrl" class="commit-hash-link monospace">{{ commit.shortSha }}</a>
+ pushed by
+ <a :href="commit.author.webUrl" class="commit-author-link">
+ {{ commit.author.name }}
+ </a>
+ </span>
+ </div>
+ <div class="item-time">
+ <total-time :time="commit.totalTime"></total-time>
+ </div>
+ </li>
+ </ul>
+ </div>
+ `,
+});
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_production_component.js b/app/assets/javascripts/cycle_analytics/components/stage_production_component.js
index 698a79ca68c..657f5385374 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_production_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/stage_production_component.js
@@ -2,48 +2,47 @@
import Vue from 'vue';
-((global) => {
- global.cycleAnalytics = global.cycleAnalytics || {};
+const global = window.gl || (window.gl = {});
+global.cycleAnalytics = global.cycleAnalytics || {};
- global.cycleAnalytics.StageProductionComponent = Vue.extend({
- props: {
- items: Array,
- stage: Object,
- },
- template: `
- <div>
- <div class="events-description">
- {{ stage.description }}
- <limit-warning :count="items.length" />
- </div>
- <ul class="stage-event-list">
- <li v-for="issue in items" class="stage-event-item">
- <div class="item-details">
- <img class="avatar" :src="issue.author.avatarUrl">
- <h5 class="item-title issue-title">
- <a class="issue-title" :href="issue.url">
- {{ issue.title }}
- </a>
- </h5>
- <a :href="issue.url" class="issue-link">#{{ issue.iid }}</a>
- &middot;
- <span>
- Opened
- <a :href="issue.url" class="issue-date">{{ issue.createdAt }}</a>
- </span>
- <span>
- by
- <a :href="issue.author.webUrl" class="issue-author-link">
- {{ issue.author.name }}
- </a>
- </span>
- </div>
- <div class="item-time">
- <total-time :time="issue.totalTime"></total-time>
- </div>
- </li>
- </ul>
+global.cycleAnalytics.StageProductionComponent = Vue.extend({
+ props: {
+ items: Array,
+ stage: Object,
+ },
+ template: `
+ <div>
+ <div class="events-description">
+ {{ stage.description }}
+ <limit-warning :count="items.length" />
</div>
- `,
- });
-})(window.gl || (window.gl = {}));
+ <ul class="stage-event-list">
+ <li v-for="issue in items" class="stage-event-item">
+ <div class="item-details">
+ <img class="avatar" :src="issue.author.avatarUrl">
+ <h5 class="item-title issue-title">
+ <a class="issue-title" :href="issue.url">
+ {{ issue.title }}
+ </a>
+ </h5>
+ <a :href="issue.url" class="issue-link">#{{ issue.iid }}</a>
+ &middot;
+ <span>
+ Opened
+ <a :href="issue.url" class="issue-date">{{ issue.createdAt }}</a>
+ </span>
+ <span>
+ by
+ <a :href="issue.author.webUrl" class="issue-author-link">
+ {{ issue.author.name }}
+ </a>
+ </span>
+ </div>
+ <div class="item-time">
+ <total-time :time="issue.totalTime"></total-time>
+ </div>
+ </li>
+ </ul>
+ </div>
+ `,
+});
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_review_component.js b/app/assets/javascripts/cycle_analytics/components/stage_review_component.js
index e63c41f2a57..8a801300647 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_review_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/stage_review_component.js
@@ -2,58 +2,57 @@
import Vue from 'vue';
-((global) => {
- global.cycleAnalytics = global.cycleAnalytics || {};
+const global = window.gl || (window.gl = {});
+global.cycleAnalytics = global.cycleAnalytics || {};
- global.cycleAnalytics.StageReviewComponent = Vue.extend({
- props: {
- items: Array,
- stage: Object,
- },
- template: `
- <div>
- <div class="events-description">
- {{ stage.description }}
- <limit-warning :count="items.length" />
- </div>
- <ul class="stage-event-list">
- <li v-for="mergeRequest in items" class="stage-event-item">
- <div class="item-details">
- <img class="avatar" :src="mergeRequest.author.avatarUrl">
- <h5 class="item-title merge-merquest-title">
- <a :href="mergeRequest.url">
- {{ mergeRequest.title }}
- </a>
- </h5>
- <a :href="mergeRequest.url" class="issue-link">!{{ mergeRequest.iid }}</a>
- &middot;
- <span>
- Opened
- <a :href="mergeRequest.url" class="issue-date">{{ mergeRequest.createdAt }}</a>
+global.cycleAnalytics.StageReviewComponent = Vue.extend({
+ props: {
+ items: Array,
+ stage: Object,
+ },
+ template: `
+ <div>
+ <div class="events-description">
+ {{ stage.description }}
+ <limit-warning :count="items.length" />
+ </div>
+ <ul class="stage-event-list">
+ <li v-for="mergeRequest in items" class="stage-event-item">
+ <div class="item-details">
+ <img class="avatar" :src="mergeRequest.author.avatarUrl">
+ <h5 class="item-title merge-merquest-title">
+ <a :href="mergeRequest.url">
+ {{ mergeRequest.title }}
+ </a>
+ </h5>
+ <a :href="mergeRequest.url" class="issue-link">!{{ mergeRequest.iid }}</a>
+ &middot;
+ <span>
+ Opened
+ <a :href="mergeRequest.url" class="issue-date">{{ mergeRequest.createdAt }}</a>
+ </span>
+ <span>
+ by
+ <a :href="mergeRequest.author.webUrl" class="issue-author-link">{{ mergeRequest.author.name }}</a>
+ </span>
+ <template v-if="mergeRequest.state === 'closed'">
+ <span class="merge-request-state">
+ <i class="fa fa-ban"></i>
+ {{ mergeRequest.state.toUpperCase() }}
</span>
- <span>
- by
- <a :href="mergeRequest.author.webUrl" class="issue-author-link">{{ mergeRequest.author.name }}</a>
+ </template>
+ <template v-else>
+ <span class="merge-request-branch" v-if="mergeRequest.branch">
+ <i class= "fa fa-code-fork"></i>
+ <a :href="mergeRequest.branch.url">{{ mergeRequest.branch.name }}</a>
</span>
- <template v-if="mergeRequest.state === 'closed'">
- <span class="merge-request-state">
- <i class="fa fa-ban"></i>
- {{ mergeRequest.state.toUpperCase() }}
- </span>
- </template>
- <template v-else>
- <span class="merge-request-branch" v-if="mergeRequest.branch">
- <i class= "fa fa-code-fork"></i>
- <a :href="mergeRequest.branch.url">{{ mergeRequest.branch.name }}</a>
- </span>
- </template>
- </div>
- <div class="item-time">
- <total-time :time="mergeRequest.totalTime"></total-time>
- </div>
- </li>
- </ul>
- </div>
- `,
- });
-})(window.gl || (window.gl = {}));
+ </template>
+ </div>
+ <div class="item-time">
+ <total-time :time="mergeRequest.totalTime"></total-time>
+ </div>
+ </li>
+ </ul>
+ </div>
+ `,
+});
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js b/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js
index d51f7134e25..4a286379588 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js
@@ -2,48 +2,47 @@
import Vue from 'vue';
import iconBranch from '../svg/icon_branch.svg';
-((global) => {
- global.cycleAnalytics = global.cycleAnalytics || {};
+const global = window.gl || (window.gl = {});
+global.cycleAnalytics = global.cycleAnalytics || {};
- global.cycleAnalytics.StageStagingComponent = Vue.extend({
- props: {
- items: Array,
- stage: Object,
- },
- data() {
- return { iconBranch };
- },
- template: `
- <div>
- <div class="events-description">
- {{ stage.description }}
- <limit-warning :count="items.length" />
- </div>
- <ul class="stage-event-list">
- <li v-for="build in items" class="stage-event-item item-build-component">
- <div class="item-details">
- <img class="avatar" :src="build.author.avatarUrl">
- <h5 class="item-title">
- <a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
- <i class="fa fa-code-fork"></i>
- <a :href="build.branch.url" class="branch-name monospace">{{ build.branch.name }}</a>
- <span class="icon-branch">${iconBranch}</span>
- <a :href="build.commitUrl" class="short-sha monospace">{{ build.shortSha }}</a>
- </h5>
- <span>
- <a :href="build.url" class="build-date">{{ build.date }}</a>
- by
- <a :href="build.author.webUrl" class="issue-author-link">
- {{ build.author.name }}
- </a>
- </span>
- </div>
- <div class="item-time">
- <total-time :time="build.totalTime"></total-time>
- </div>
- </li>
- </ul>
+global.cycleAnalytics.StageStagingComponent = Vue.extend({
+ props: {
+ items: Array,
+ stage: Object,
+ },
+ data() {
+ return { iconBranch };
+ },
+ template: `
+ <div>
+ <div class="events-description">
+ {{ stage.description }}
+ <limit-warning :count="items.length" />
</div>
- `,
- });
-})(window.gl || (window.gl = {}));
+ <ul class="stage-event-list">
+ <li v-for="build in items" class="stage-event-item item-build-component">
+ <div class="item-details">
+ <img class="avatar" :src="build.author.avatarUrl">
+ <h5 class="item-title">
+ <a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
+ <i class="fa fa-code-fork"></i>
+ <a :href="build.branch.url" class="branch-name monospace">{{ build.branch.name }}</a>
+ <span class="icon-branch">${iconBranch}</span>
+ <a :href="build.commitUrl" class="short-sha monospace">{{ build.shortSha }}</a>
+ </h5>
+ <span>
+ <a :href="build.url" class="build-date">{{ build.date }}</a>
+ by
+ <a :href="build.author.webUrl" class="issue-author-link">
+ {{ build.author.name }}
+ </a>
+ </span>
+ </div>
+ <div class="item-time">
+ <total-time :time="build.totalTime"></total-time>
+ </div>
+ </li>
+ </ul>
+ </div>
+ `,
+});
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_test_component.js b/app/assets/javascripts/cycle_analytics/components/stage_test_component.js
index 17ae3a9ddc1..e306026429e 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_test_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/stage_test_component.js
@@ -3,48 +3,47 @@ import Vue from 'vue';
import iconBuildStatus from '../svg/icon_build_status.svg';
import iconBranch from '../svg/icon_branch.svg';
-((global) => {
- global.cycleAnalytics = global.cycleAnalytics || {};
+const global = window.gl || (window.gl = {});
+global.cycleAnalytics = global.cycleAnalytics || {};
- global.cycleAnalytics.StageTestComponent = Vue.extend({
- props: {
- items: Array,
- stage: Object,
- },
- data() {
- return { iconBuildStatus, iconBranch };
- },
- template: `
- <div>
- <div class="events-description">
- {{ stage.description }}
- <limit-warning :count="items.length" />
- </div>
- <ul class="stage-event-list">
- <li v-for="build in items" class="stage-event-item item-build-component">
- <div class="item-details">
- <h5 class="item-title">
- <span class="icon-build-status">${iconBuildStatus}</span>
- <a :href="build.url" class="item-build-name">{{ build.name }}</a>
- &middot;
- <a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
- <i class="fa fa-code-fork"></i>
- <a :href="build.branch.url" class="branch-name monospace">{{ build.branch.name }}</a>
- <span class="icon-branch">${iconBranch}</span>
- <a :href="build.commitUrl" class="short-sha monospace">{{ build.shortSha }}</a>
- </h5>
- <span>
- <a :href="build.url" class="issue-date">
- {{ build.date }}
- </a>
- </span>
- </div>
- <div class="item-time">
- <total-time :time="build.totalTime"></total-time>
- </div>
- </li>
- </ul>
+global.cycleAnalytics.StageTestComponent = Vue.extend({
+ props: {
+ items: Array,
+ stage: Object,
+ },
+ data() {
+ return { iconBuildStatus, iconBranch };
+ },
+ template: `
+ <div>
+ <div class="events-description">
+ {{ stage.description }}
+ <limit-warning :count="items.length" />
</div>
- `,
- });
-})(window.gl || (window.gl = {}));
+ <ul class="stage-event-list">
+ <li v-for="build in items" class="stage-event-item item-build-component">
+ <div class="item-details">
+ <h5 class="item-title">
+ <span class="icon-build-status">${iconBuildStatus}</span>
+ <a :href="build.url" class="item-build-name">{{ build.name }}</a>
+ &middot;
+ <a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
+ <i class="fa fa-code-fork"></i>
+ <a :href="build.branch.url" class="branch-name monospace">{{ build.branch.name }}</a>
+ <span class="icon-branch">${iconBranch}</span>
+ <a :href="build.commitUrl" class="short-sha monospace">{{ build.shortSha }}</a>
+ </h5>
+ <span>
+ <a :href="build.url" class="issue-date">
+ {{ build.date }}
+ </a>
+ </span>
+ </div>
+ <div class="item-time">
+ <total-time :time="build.totalTime"></total-time>
+ </div>
+ </li>
+ </ul>
+ </div>
+ `,
+});
diff --git a/app/assets/javascripts/cycle_analytics/components/total_time_component.js b/app/assets/javascripts/cycle_analytics/components/total_time_component.js
index b4442ea5566..77edcb76273 100644
--- a/app/assets/javascripts/cycle_analytics/components/total_time_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/total_time_component.js
@@ -2,25 +2,24 @@
import Vue from 'vue';
-((global) => {
- global.cycleAnalytics = global.cycleAnalytics || {};
+const global = window.gl || (window.gl = {});
+global.cycleAnalytics = global.cycleAnalytics || {};
- global.cycleAnalytics.TotalTimeComponent = Vue.extend({
- props: {
- time: Object,
- },
- template: `
- <span class="total-time">
- <template v-if="Object.keys(time).length">
- <template v-if="time.days">{{ time.days }} <span>{{ time.days === 1 ? 'day' : 'days' }}</span></template>
- <template v-if="time.hours">{{ time.hours }} <span>hr</span></template>
- <template v-if="time.mins && !time.days">{{ time.mins }} <span>mins</span></template>
- <template v-if="time.seconds && Object.keys(time).length === 1 || time.seconds === 0">{{ time.seconds }} <span>s</span></template>
- </template>
- <template v-else>
- --
- </template>
- </span>
- `,
- });
-})(window.gl || (window.gl = {}));
+global.cycleAnalytics.TotalTimeComponent = Vue.extend({
+ props: {
+ time: Object,
+ },
+ template: `
+ <span class="total-time">
+ <template v-if="Object.keys(time).length">
+ <template v-if="time.days">{{ time.days }} <span>{{ time.days === 1 ? 'day' : 'days' }}</span></template>
+ <template v-if="time.hours">{{ time.hours }} <span>hr</span></template>
+ <template v-if="time.mins && !time.days">{{ time.mins }} <span>mins</span></template>
+ <template v-if="time.seconds && Object.keys(time).length === 1 || time.seconds === 0">{{ time.seconds }} <span>s</span></template>
+ </template>
+ <template v-else>
+ --
+ </template>
+ </span>
+ `,
+});
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
index b099b39e58f..48cab437e02 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
@@ -125,7 +125,7 @@ $(() => {
},
dismissOverviewDialog() {
this.isOverviewDialogDismissed = true;
- Cookies.set(OVERVIEW_DIALOG_COOKIE, '1');
+ Cookies.set(OVERVIEW_DIALOG_COOKIE, '1', { expires: 365 });
},
},
});
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_service.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_service.js
index 9f74b14c4b9..681d6eef565 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_service.js
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_service.js
@@ -1,41 +1,41 @@
/* eslint-disable no-param-reassign */
-((global) => {
- global.cycleAnalytics = global.cycleAnalytics || {};
- class CycleAnalyticsService {
- constructor(options) {
- this.requestPath = options.requestPath;
- }
+const global = window.gl || (window.gl = {});
+global.cycleAnalytics = global.cycleAnalytics || {};
- fetchCycleAnalyticsData(options) {
- options = options || { startDate: 30 };
-
- return $.ajax({
- url: this.requestPath,
- method: 'GET',
- dataType: 'json',
- contentType: 'application/json',
- data: {
- cycle_analytics: {
- start_date: options.startDate,
- },
- },
- });
- }
+class CycleAnalyticsService {
+ constructor(options) {
+ this.requestPath = options.requestPath;
+ }
- fetchStageData(options) {
- const {
- stage,
- startDate,
- } = options;
+ fetchCycleAnalyticsData(options) {
+ options = options || { startDate: 30 };
- return $.get(`${this.requestPath}/events/${stage.title.toLowerCase()}.json`, {
+ return $.ajax({
+ url: this.requestPath,
+ method: 'GET',
+ dataType: 'json',
+ contentType: 'application/json',
+ data: {
cycle_analytics: {
- start_date: startDate,
+ start_date: options.startDate,
},
- });
- }
+ },
+ });
+ }
+
+ fetchStageData(options) {
+ const {
+ stage,
+ startDate,
+ } = options;
+
+ return $.get(`${this.requestPath}/events/${stage.title.toLowerCase()}.json`, {
+ cycle_analytics: {
+ start_date: startDate,
+ },
+ });
}
+}
- global.cycleAnalytics.CycleAnalyticsService = CycleAnalyticsService;
-})(window.gl || (window.gl = {}));
+global.cycleAnalytics.CycleAnalyticsService = CycleAnalyticsService;
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js
index 7ae9de7297c..6536a8fd7fa 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js
@@ -3,102 +3,101 @@
require('../lib/utils/text_utility');
const DEFAULT_EVENT_OBJECTS = require('./default_event_objects');
-((global) => {
- global.cycleAnalytics = global.cycleAnalytics || {};
+const global = window.gl || (window.gl = {});
+global.cycleAnalytics = global.cycleAnalytics || {};
- const EMPTY_STAGE_TEXTS = {
- issue: 'The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.',
- plan: 'The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.',
- code: 'The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.',
- test: 'The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.',
- review: 'The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.',
- staging: 'The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.',
- production: 'The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.',
- };
+const EMPTY_STAGE_TEXTS = {
+ issue: 'The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.',
+ plan: 'The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.',
+ code: 'The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.',
+ test: 'The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.',
+ review: 'The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.',
+ staging: 'The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.',
+ production: 'The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.',
+};
- global.cycleAnalytics.CycleAnalyticsStore = {
- state: {
- summary: '',
- stats: '',
- analytics: '',
- events: [],
- stages: [],
- },
- setCycleAnalyticsData(data) {
- this.state = Object.assign(this.state, this.decorateData(data));
- },
- decorateData(data) {
- const newData = {};
+global.cycleAnalytics.CycleAnalyticsStore = {
+ state: {
+ summary: '',
+ stats: '',
+ analytics: '',
+ events: [],
+ stages: [],
+ },
+ setCycleAnalyticsData(data) {
+ this.state = Object.assign(this.state, this.decorateData(data));
+ },
+ decorateData(data) {
+ const newData = {};
- newData.stages = data.stats || [];
- newData.summary = data.summary || [];
+ newData.stages = data.stats || [];
+ newData.summary = data.summary || [];
- newData.summary.forEach((item) => {
- item.value = item.value || '-';
- });
+ newData.summary.forEach((item) => {
+ item.value = item.value || '-';
+ });
- newData.stages.forEach((item) => {
- const stageSlug = gl.text.dasherize(item.title.toLowerCase());
- item.active = false;
- item.isUserAllowed = data.permissions[stageSlug];
- item.emptyStageText = EMPTY_STAGE_TEXTS[stageSlug];
- item.component = `stage-${stageSlug}-component`;
- item.slug = stageSlug;
- });
- newData.analytics = data;
- return newData;
- },
- setLoadingState(state) {
- this.state.isLoading = state;
- },
- setErrorState(state) {
- this.state.hasError = state;
- },
- deactivateAllStages() {
- this.state.stages.forEach((stage) => {
- stage.active = false;
- });
- },
- setActiveStage(stage) {
- this.deactivateAllStages();
- stage.active = true;
- },
- setStageEvents(events, stage) {
- this.state.events = this.decorateEvents(events, stage);
- },
- decorateEvents(events, stage) {
- const newEvents = [];
+ newData.stages.forEach((item) => {
+ const stageSlug = gl.text.dasherize(item.title.toLowerCase());
+ item.active = false;
+ item.isUserAllowed = data.permissions[stageSlug];
+ item.emptyStageText = EMPTY_STAGE_TEXTS[stageSlug];
+ item.component = `stage-${stageSlug}-component`;
+ item.slug = stageSlug;
+ });
+ newData.analytics = data;
+ return newData;
+ },
+ setLoadingState(state) {
+ this.state.isLoading = state;
+ },
+ setErrorState(state) {
+ this.state.hasError = state;
+ },
+ deactivateAllStages() {
+ this.state.stages.forEach((stage) => {
+ stage.active = false;
+ });
+ },
+ setActiveStage(stage) {
+ this.deactivateAllStages();
+ stage.active = true;
+ },
+ setStageEvents(events, stage) {
+ this.state.events = this.decorateEvents(events, stage);
+ },
+ decorateEvents(events, stage) {
+ const newEvents = [];
- events.forEach((item) => {
- if (!item) return;
+ events.forEach((item) => {
+ if (!item) return;
- const eventItem = Object.assign({}, DEFAULT_EVENT_OBJECTS[stage.slug], item);
+ const eventItem = Object.assign({}, DEFAULT_EVENT_OBJECTS[stage.slug], item);
- eventItem.totalTime = eventItem.total_time;
+ eventItem.totalTime = eventItem.total_time;
- if (eventItem.author) {
- eventItem.author.webUrl = eventItem.author.web_url;
- eventItem.author.avatarUrl = eventItem.author.avatar_url;
- }
+ if (eventItem.author) {
+ eventItem.author.webUrl = eventItem.author.web_url;
+ eventItem.author.avatarUrl = eventItem.author.avatar_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;
+ 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 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;
+ 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(eventItem);
- });
+ newEvents.push(eventItem);
+ });
- return newEvents;
- },
- currentActiveStage() {
- return this.state.stages.find(stage => stage.active);
- },
- };
-})(window.gl || (window.gl = {}));
+ return newEvents;
+ },
+ currentActiveStage() {
+ return this.state.stages.find(stage => stage.active);
+ },
+};
diff --git a/app/assets/javascripts/diff_notes/components/resolve_btn.js b/app/assets/javascripts/diff_notes/components/resolve_btn.js
index 312f38ce241..dcfc40c1013 100644
--- a/app/assets/javascripts/diff_notes/components/resolve_btn.js
+++ b/app/assets/javascripts/diff_notes/components/resolve_btn.js
@@ -20,8 +20,7 @@ import Vue from 'vue';
data: function () {
return {
discussions: CommentsStore.state,
- loading: false,
- note: {},
+ loading: false
};
},
watch: {
@@ -34,6 +33,9 @@ import Vue from 'vue';
discussion: function () {
return this.discussions[this.discussionId];
},
+ note: function () {
+ return this.discussion ? this.discussion.getNote(this.noteId) : {};
+ },
buttonText: function () {
if (this.isResolved) {
return `Resolved by ${this.resolvedByName}`;
@@ -112,8 +114,6 @@ import Vue from 'vue';
authorAvatar: this.authorAvatar,
noteTruncated: this.noteTruncated,
});
-
- this.note = this.discussion.getNote(this.noteId);
}
});
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 4e68f9c77e9..d88b7cd0e17 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -33,10 +33,12 @@
/* global Labels */
/* global Shortcuts */
/* global Sidebar */
+/* global ShortcutsWiki */
import Issue from './issue';
import BindInOut from './behaviors/bind_in_out';
+import Group from './group';
import GroupName from './group_name';
import GroupsList from './groups_list';
import ProjectsList from './projects_list';
@@ -45,6 +47,7 @@ import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater';
import BlobForkSuggestion from './blob/blob_fork_suggestion';
import UserCallout from './user_callout';
import { ProtectedTagCreate, ProtectedTagEditList } from './protected_tags';
+import ShortcutsWiki from './shortcuts_wiki';
const ShortcutsBlob = require('./shortcuts_blob');
@@ -271,8 +274,9 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'groups:create':
case 'admin:groups:create':
BindInOut.initAll();
- case 'groups:new':
- case 'admin:groups:new':
+ new Group();
+ new GroupAvatar();
+ break;
case 'groups:edit':
case 'admin:groups:edit':
new GroupAvatar();
@@ -414,7 +418,7 @@ const ShortcutsBlob = require('./shortcuts_blob');
break;
case 'wikis':
new gl.Wikis();
- shortcut_handler = new ShortcutsNavigation();
+ shortcut_handler = new ShortcutsWiki();
new ZenMode();
new gl.GLForm($('.wiki-form'));
break;
diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js
index f2963a5eb19..df0e3f46827 100644
--- a/app/assets/javascripts/dropzone_input.js
+++ b/app/assets/javascripts/dropzone_input.js
@@ -66,7 +66,10 @@ window.DropzoneInput = (function() {
form_textarea.focus();
},
success: function(header, response) {
- pasteText(response.link.markdown);
+ const processingFileCount = this.getQueuedFiles().length + this.getUploadingFiles().length;
+ const shouldPad = processingFileCount >= 1;
+
+ pasteText(response.link.markdown, shouldPad);
},
error: function(temp) {
var checkIfMsgExists, errorAlert;
@@ -123,9 +126,10 @@ window.DropzoneInput = (function() {
}
return false;
};
- pasteText = function(text) {
+ pasteText = function(text, shouldPad) {
var afterSelection, beforeSelection, caretEnd, caretStart, textEnd;
- var formattedText = text + "\n\n";
+ var formattedText = text;
+ if (shouldPad) formattedText += "\n\n";
caretStart = $(child)[0].selectionStart;
caretEnd = $(child)[0].selectionEnd;
textEnd = $(child).val().length;
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index 9ac4c49d697..b62b2cec4d8 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -50,7 +50,7 @@ window.gl.GfmAutoComplete = {
template: '<li>${title}</li>'
},
Loading: {
- template: '<li style="pointer-events: none;"><i class="fa fa-refresh fa-spin"></i> Loading...</li>'
+ template: '<li style="pointer-events: none;"><i class="fa fa-spinner fa-spin"></i> Loading...</li>'
},
DefaultOptions: {
sorter: function(query, items, searchKey) {
diff --git a/app/assets/javascripts/group.js b/app/assets/javascripts/group.js
new file mode 100644
index 00000000000..7732edde1e7
--- /dev/null
+++ b/app/assets/javascripts/group.js
@@ -0,0 +1,21 @@
+export default class Group {
+ constructor() {
+ this.groupPath = $('#group_path');
+ this.groupName = $('#group_name');
+ this.updateHandler = this.update.bind(this);
+ this.resetHandler = this.reset.bind(this);
+ if (this.groupName.val() === '') {
+ this.groupPath.on('keyup', this.updateHandler);
+ this.groupName.on('keydown', this.resetHandler);
+ }
+ }
+
+ update() {
+ this.groupName.val(this.groupPath.val());
+ }
+
+ reset() {
+ this.groupPath.off('keyup', this.updateHandler);
+ this.groupName.off('keydown', this.resetHandler);
+ }
+}
diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js
index 47e675f537e..011043e992f 100644
--- a/app/assets/javascripts/issue.js
+++ b/app/assets/javascripts/issue.js
@@ -20,57 +20,60 @@ class Issue {
});
Issue.initIssueBtnEventListeners();
}
+
+ Issue.$btnNewBranch = $('#new-branch');
+
Issue.initMergeRequests();
Issue.initRelatedBranches();
Issue.initCanCreateBranch();
}
static initIssueBtnEventListeners() {
- var issueFailMessage;
- issueFailMessage = 'Unable to update this issue at this time.';
- return $('a.btn-close, a.btn-reopen').on('click', function(e) {
- var $this, isClose, shouldSubmit, url;
+ const issueFailMessage = 'Unable to update this issue at this time.';
+
+ const closeButtons = $('a.btn-close');
+ const isClosedBadge = $('div.status-box-closed');
+ const isOpenBadge = $('div.status-box-open');
+ const projectIssuesCounter = $('.issue_counter');
+ const reopenButtons = $('a.btn-reopen');
+
+ return closeButtons.add(reopenButtons).on('click', function(e) {
+ var $this, shouldSubmit, url;
e.preventDefault();
e.stopImmediatePropagation();
$this = $(this);
- isClose = $this.hasClass('btn-close');
shouldSubmit = $this.hasClass('btn-comment');
if (shouldSubmit) {
Issue.submitNoteForm($this.closest('form'));
}
$this.prop('disabled', true);
+ Issue.setNewBranchButtonState(true, null);
url = $this.attr('href');
return $.ajax({
type: 'PUT',
- url: url,
- error: function(jqXHR, textStatus, errorThrown) {
- var issueStatus;
- issueStatus = isClose ? 'close' : 'open';
- return new Flash(issueFailMessage, 'alert');
- },
- success: function(data, textStatus, jqXHR) {
- if ('id' in data) {
- $(document).trigger('issuable:change');
- let total = Number($('.issue_counter').text().replace(/[^\d]/, ''));
- if (isClose) {
- $('a.btn-close').addClass('hidden');
- $('a.btn-reopen').removeClass('hidden');
- $('div.status-box-closed').removeClass('hidden');
- $('div.status-box-open').addClass('hidden');
- total -= 1;
- } else {
- $('a.btn-reopen').addClass('hidden');
- $('a.btn-close').removeClass('hidden');
- $('div.status-box-closed').addClass('hidden');
- $('div.status-box-open').removeClass('hidden');
- total += 1;
- }
- $('.issue_counter').text(gl.text.addDelimiter(total));
- } else {
- new Flash(issueFailMessage, 'alert');
- }
- return $this.prop('disabled', false);
+ url: url
+ }).fail(function(jqXHR, textStatus, errorThrown) {
+ new Flash(issueFailMessage);
+ Issue.initCanCreateBranch();
+ }).done(function(data, textStatus, jqXHR) {
+ if ('id' in data) {
+ $(document).trigger('issuable:change');
+
+ const isClosed = $this.hasClass('btn-close');
+ closeButtons.toggleClass('hidden', isClosed);
+ reopenButtons.toggleClass('hidden', !isClosed);
+ isClosedBadge.toggleClass('hidden', !isClosed);
+ isOpenBadge.toggleClass('hidden', isClosed);
+
+ let numProjectIssues = Number(projectIssuesCounter.text().replace(/[^\d]/, ''));
+ numProjectIssues = isClosed ? numProjectIssues - 1 : numProjectIssues + 1;
+ projectIssuesCounter.text(gl.text.addDelimiter(numProjectIssues));
+ } else {
+ new Flash(issueFailMessage);
}
+
+ $this.prop('disabled', false);
+ Issue.initCanCreateBranch();
});
});
}
@@ -86,9 +89,9 @@ class Issue {
static initMergeRequests() {
var $container;
$container = $('#merge-requests');
- return $.getJSON($container.data('url')).error(function() {
- return new Flash('Failed to load referenced merge requests', 'alert');
- }).success(function(data) {
+ return $.getJSON($container.data('url')).fail(function() {
+ return new Flash('Failed to load referenced merge requests');
+ }).done(function(data) {
if ('html' in data) {
return $container.html(data.html);
}
@@ -98,9 +101,9 @@ class Issue {
static initRelatedBranches() {
var $container;
$container = $('#related-branches');
- return $.getJSON($container.data('url')).error(function() {
- return new Flash('Failed to load related branches', 'alert');
- }).success(function(data) {
+ return $.getJSON($container.data('url')).fail(function() {
+ return new Flash('Failed to load related branches');
+ }).done(function(data) {
if ('html' in data) {
return $container.html(data.html);
}
@@ -108,24 +111,27 @@ class Issue {
}
static initCanCreateBranch() {
- var $container;
- $container = $('#new-branch');
// If the user doesn't have the required permissions the container isn't
// rendered at all.
- if ($container.length === 0) {
+ if (Issue.$btnNewBranch.length === 0) {
return;
}
- return $.getJSON($container.data('path')).error(function() {
- $container.find('.unavailable').show();
- return new Flash('Failed to check if a new branch can be created.', 'alert');
- }).success(function(data) {
- if (data.can_create_branch) {
- $container.find('.available').show();
- } else {
- return $container.find('.unavailable').show();
- }
+ return $.getJSON(Issue.$btnNewBranch.data('path')).fail(function() {
+ Issue.setNewBranchButtonState(false, false);
+ new Flash('Failed to check if a new branch can be created.');
+ }).done(function(data) {
+ Issue.setNewBranchButtonState(false, data.can_create_branch);
});
}
+
+ static setNewBranchButtonState(isPending, canCreate) {
+ if (Issue.$btnNewBranch.length === 0) {
+ return;
+ }
+
+ Issue.$btnNewBranch.find('.available').toggle(!isPending && canCreate);
+ Issue.$btnNewBranch.find('.unavailable').toggle(!isPending && !canCreate);
+ }
}
export default Issue;
diff --git a/app/assets/javascripts/issue_show/index.js b/app/assets/javascripts/issue_show/index.js
index b6ce8e83729..4d491e70d83 100644
--- a/app/assets/javascripts/issue_show/index.js
+++ b/app/assets/javascripts/issue_show/index.js
@@ -1,26 +1,20 @@
import Vue from 'vue';
-import IssueTitle from './issue_title';
+import IssueTitle from './issue_title.vue';
import '../vue_shared/vue_resource_interceptor';
-const vueOptions = () => ({
- el: '.issue-title-entrypoint',
- components: {
- IssueTitle,
- },
- data() {
- const issueTitleData = document.querySelector('.issue-title-data').dataset;
+(() => {
+ const issueTitleData = document.querySelector('.issue-title-data').dataset;
+ const { initialTitle, endpoint } = issueTitleData;
- return {
- initialTitle: issueTitleData.initialTitle,
- endpoint: issueTitleData.endpoint,
- };
- },
- template: `
- <IssueTitle
- :initialTitle="initialTitle"
- :endpoint="endpoint"
- />
- `,
-});
+ const vm = new Vue({
+ el: '.issue-title-entrypoint',
+ render: createElement => createElement(IssueTitle, {
+ props: {
+ initialTitle,
+ endpoint,
+ },
+ }),
+ });
-(() => new Vue(vueOptions()))();
+ return vm;
+})();
diff --git a/app/assets/javascripts/issue_show/issue_title.js b/app/assets/javascripts/issue_show/issue_title.vue
index 1184c8956dc..ba54178a310 100644
--- a/app/assets/javascripts/issue_show/issue_title.js
+++ b/app/assets/javascripts/issue_show/issue_title.vue
@@ -1,3 +1,4 @@
+<script>
import Visibility from 'visibilityjs';
import Poll from './../lib/utils/poll';
import Service from './services/index';
@@ -72,7 +73,9 @@ export default {
created() {
this.fetch();
},
- template: `
- <h2 class='title' v-html='title'></h2>
- `,
};
+</script>
+
+<template>
+ <h2 class="title" v-html="title"></h2>
+</template>
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index e1e6ca25446..01c4b9821d3 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -47,6 +47,10 @@
}
};
+ gl.utils.updateTooltipTitle = function($tooltipEl, newTitle) {
+ return $tooltipEl.attr('title', newTitle).tooltip('fixTitle');
+ };
+
w.gl.utils.disableButtonIfEmptyField = function(field_selector, button_selector, event_name) {
event_name = event_name || 'input';
var closest_submit, field, that;
@@ -364,9 +368,9 @@
});
};
- w.gl.utils.setFavicon = (iconName) => {
- if (faviconEl && iconName) {
- faviconEl.setAttribute('href', `/assets/${iconName}.ico`);
+ w.gl.utils.setFavicon = (faviconPath) => {
+ if (faviconEl && faviconPath) {
+ faviconEl.setAttribute('href', faviconPath);
}
};
@@ -381,8 +385,8 @@
url: pageUrl,
dataType: 'json',
success: function(data) {
- if (data && data.icon) {
- gl.utils.setFavicon(`ci_favicons/${data.icon}`);
+ if (data && data.favicon) {
+ gl.utils.setFavicon(data.favicon);
} else {
gl.utils.resetFavicon();
}
diff --git a/app/assets/javascripts/lib/utils/constants.js b/app/assets/javascripts/lib/utils/constants.js
new file mode 100644
index 00000000000..1e96c7ab5cd
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/constants.js
@@ -0,0 +1,2 @@
+/* eslint-disable import/prefer-default-export */
+export const BYTES_IN_KIB = 1024;
diff --git a/app/assets/javascripts/lib/utils/number_utils.js b/app/assets/javascripts/lib/utils/number_utils.js
index e2bf69ee52e..f1b07408671 100644
--- a/app/assets/javascripts/lib/utils/number_utils.js
+++ b/app/assets/javascripts/lib/utils/number_utils.js
@@ -1,4 +1,4 @@
-/* eslint-disable import/prefer-default-export */
+import { BYTES_IN_KIB } from './constants';
/**
* Function that allows a number with an X amount of decimals
@@ -32,3 +32,13 @@ export function formatRelevantDigits(number) {
}
return formattedNumber;
}
+
+/**
+ * Utility function that calculates KiB of the given bytes.
+ *
+ * @param {Number} number bytes
+ * @return {Number} KiB
+ */
+export function bytesToKiB(number) {
+ return number / BYTES_IN_KIB;
+}
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index 2e5f8a09fc1..fecd531328d 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -1,192 +1,188 @@
-/* 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 */
-
+/* 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, vars-on-top */
require('vendor/latinise');
-(function() {
- (function(w) {
- var base;
- if (w.gl == null) {
- w.gl = {};
- }
- if ((base = w.gl).text == null) {
- base.text = {};
+var base;
+var w = window;
+if (w.gl == null) {
+ w.gl = {};
+}
+if ((base = w.gl).text == null) {
+ base.text = {};
+}
+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);
+};
+gl.text.replaceRange = function(s, start, end, substitute) {
+ return s.substring(0, start) + substitute + s.substring(end);
+};
+gl.text.getTextWidth = function(text, font) {
+ /**
+ * Uses canvas.measureText to compute and return the width of the given text of given font in pixels.
+ *
+ * @param {String} text The text to be rendered.
+ * @param {String} font The css font descriptor that text is to be rendered with (e.g. "bold 14px verdana").
+ *
+ * @see http://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393
+ */
+ // re-use canvas object for better performance
+ var canvas = gl.text.getTextWidth.canvas || (gl.text.getTextWidth.canvas = document.createElement('canvas'));
+ var context = canvas.getContext('2d');
+ context.font = font;
+ return context.measureText(text).width;
+};
+gl.text.selectedText = function(text, textarea) {
+ return text.substring(textarea.selectionStart, textarea.selectionEnd);
+};
+gl.text.lineBefore = function(text, textarea) {
+ var split;
+ split = text.substring(0, textarea.selectionStart).trim().split('\n');
+ return split[split.length - 1];
+};
+gl.text.lineAfter = function(text, textarea) {
+ return text.substring(textarea.selectionEnd).trim().split('\n')[0];
+};
+gl.text.blockTagText = function(text, textArea, blockTag, selected) {
+ var lineAfter, lineBefore;
+ lineBefore = this.lineBefore(text, textArea);
+ lineAfter = this.lineAfter(text, textArea);
+ if (lineBefore === blockTag && lineAfter === blockTag) {
+ // To remove the block tag we have to select the line before & after
+ if (blockTag != null) {
+ textArea.selectionStart = textArea.selectionStart - (blockTag.length + 1);
+ textArea.selectionEnd = textArea.selectionEnd + (blockTag.length + 1);
}
- 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);
- };
- gl.text.replaceRange = function(s, start, end, substitute) {
- return s.substring(0, start) + substitute + s.substring(end);
- };
- gl.text.getTextWidth = function(text, font) {
- /**
- * Uses canvas.measureText to compute and return the width of the given text of given font in pixels.
- *
- * @param {String} text The text to be rendered.
- * @param {String} font The css font descriptor that text is to be rendered with (e.g. "bold 14px verdana").
- *
- * @see http://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393
- */
- // re-use canvas object for better performance
- var canvas = gl.text.getTextWidth.canvas || (gl.text.getTextWidth.canvas = document.createElement('canvas'));
- var context = canvas.getContext('2d');
- context.font = font;
- return context.measureText(text).width;
- };
- gl.text.selectedText = function(text, textarea) {
- return text.substring(textarea.selectionStart, textarea.selectionEnd);
- };
- gl.text.lineBefore = function(text, textarea) {
- var split;
- split = text.substring(0, textarea.selectionStart).trim().split('\n');
- return split[split.length - 1];
- };
- gl.text.lineAfter = function(text, textarea) {
- return text.substring(textarea.selectionEnd).trim().split('\n')[0];
- };
- gl.text.blockTagText = function(text, textArea, blockTag, selected) {
- var lineAfter, lineBefore;
- lineBefore = this.lineBefore(text, textArea);
- lineAfter = this.lineAfter(text, textArea);
- if (lineBefore === blockTag && lineAfter === blockTag) {
- // To remove the block tag we have to select the line before & after
- if (blockTag != null) {
- textArea.selectionStart = textArea.selectionStart - (blockTag.length + 1);
- textArea.selectionEnd = textArea.selectionEnd + (blockTag.length + 1);
- }
- return selected;
- } else {
- return blockTag + "\n" + selected + "\n" + blockTag;
- }
- };
- gl.text.insertText = function(textArea, text, tag, blockTag, selected, wrap) {
- var insertText, inserted, selectedSplit, startChar, removedLastNewLine, removedFirstNewLine, currentLineEmpty, lastNewLine;
- removedLastNewLine = false;
- removedFirstNewLine = false;
- currentLineEmpty = false;
+ return selected;
+ } else {
+ return blockTag + "\n" + selected + "\n" + blockTag;
+ }
+};
+gl.text.insertText = function(textArea, text, tag, blockTag, selected, wrap) {
+ var insertText, inserted, selectedSplit, startChar, removedLastNewLine, removedFirstNewLine, currentLineEmpty, lastNewLine;
+ removedLastNewLine = false;
+ removedFirstNewLine = false;
+ currentLineEmpty = false;
- // Remove the first newline
- if (selected.indexOf('\n') === 0) {
- removedFirstNewLine = true;
- selected = selected.replace(/\n+/, '');
- }
+ // Remove the first newline
+ if (selected.indexOf('\n') === 0) {
+ removedFirstNewLine = true;
+ selected = selected.replace(/\n+/, '');
+ }
- // Remove the last newline
- if (textArea.selectionEnd - textArea.selectionStart > selected.replace(/\n$/, '').length) {
- removedLastNewLine = true;
- selected = selected.replace(/\n$/, '');
- }
+ // Remove the last newline
+ if (textArea.selectionEnd - textArea.selectionStart > selected.replace(/\n$/, '').length) {
+ removedLastNewLine = true;
+ selected = selected.replace(/\n$/, '');
+ }
- selectedSplit = selected.split('\n');
+ selectedSplit = selected.split('\n');
- if (!wrap) {
- lastNewLine = textArea.value.substr(0, textArea.selectionStart).lastIndexOf('\n');
+ if (!wrap) {
+ lastNewLine = textArea.value.substr(0, textArea.selectionStart).lastIndexOf('\n');
- // Check whether the current line is empty or consists only of spaces(=handle as empty)
- if (/^\s*$/.test(textArea.value.substring(lastNewLine, textArea.selectionStart))) {
- currentLineEmpty = true;
- }
- }
+ // Check whether the current line is empty or consists only of spaces(=handle as empty)
+ if (/^\s*$/.test(textArea.value.substring(lastNewLine, textArea.selectionStart))) {
+ currentLineEmpty = true;
+ }
+ }
- startChar = !wrap && !currentLineEmpty && textArea.selectionStart > 0 ? '\n' : '';
+ startChar = !wrap && !currentLineEmpty && textArea.selectionStart > 0 ? '\n' : '';
- if (selectedSplit.length > 1 && (!wrap || (blockTag != null))) {
- if (blockTag != null) {
- insertText = this.blockTagText(text, textArea, blockTag, selected);
+ if (selectedSplit.length > 1 && (!wrap || (blockTag != null))) {
+ if (blockTag != null) {
+ insertText = this.blockTagText(text, textArea, blockTag, selected);
+ } else {
+ insertText = selectedSplit.map(function(val) {
+ if (val.indexOf(tag) === 0) {
+ return "" + (val.replace(tag, ''));
} else {
- insertText = selectedSplit.map(function(val) {
- if (val.indexOf(tag) === 0) {
- return "" + (val.replace(tag, ''));
- } else {
- return "" + tag + val;
- }
- }).join('\n');
+ return "" + tag + val;
}
- } else {
- insertText = "" + startChar + tag + selected + (wrap ? tag : ' ');
- }
+ }).join('\n');
+ }
+ } else {
+ insertText = "" + startChar + tag + selected + (wrap ? tag : ' ');
+ }
- if (removedFirstNewLine) {
- insertText = '\n' + insertText;
- }
+ if (removedFirstNewLine) {
+ insertText = '\n' + insertText;
+ }
- if (removedLastNewLine) {
- insertText += '\n';
- }
+ if (removedLastNewLine) {
+ insertText += '\n';
+ }
- if (document.queryCommandSupported('insertText')) {
- inserted = document.execCommand('insertText', false, insertText);
- }
- if (!inserted) {
- try {
- document.execCommand("ms-beginUndoUnit");
- } catch (error) {}
- textArea.value = this.replaceRange(text, textArea.selectionStart, textArea.selectionEnd, insertText);
- try {
- document.execCommand("ms-endUndoUnit");
- } catch (error) {}
- }
- return this.moveCursor(textArea, tag, wrap, removedLastNewLine);
- };
- gl.text.moveCursor = function(textArea, tag, wrapped, removedLastNewLine) {
- var pos;
- if (!textArea.setSelectionRange) {
- return;
- }
- if (textArea.selectionStart === textArea.selectionEnd) {
- if (wrapped) {
- pos = textArea.selectionStart - tag.length;
- } else {
- pos = textArea.selectionStart;
- }
+ if (document.queryCommandSupported('insertText')) {
+ inserted = document.execCommand('insertText', false, insertText);
+ }
+ if (!inserted) {
+ try {
+ document.execCommand("ms-beginUndoUnit");
+ } catch (error) {}
+ textArea.value = this.replaceRange(text, textArea.selectionStart, textArea.selectionEnd, insertText);
+ try {
+ document.execCommand("ms-endUndoUnit");
+ } catch (error) {}
+ }
+ return this.moveCursor(textArea, tag, wrap, removedLastNewLine);
+};
+gl.text.moveCursor = function(textArea, tag, wrapped, removedLastNewLine) {
+ var pos;
+ if (!textArea.setSelectionRange) {
+ return;
+ }
+ if (textArea.selectionStart === textArea.selectionEnd) {
+ if (wrapped) {
+ pos = textArea.selectionStart - tag.length;
+ } else {
+ pos = textArea.selectionStart;
+ }
- if (removedLastNewLine) {
- pos -= 1;
- }
+ if (removedLastNewLine) {
+ pos -= 1;
+ }
- return textArea.setSelectionRange(pos, pos);
- }
- };
- gl.text.updateText = function(textArea, tag, blockTag, wrap) {
- var $textArea, selected, text;
- $textArea = $(textArea);
- textArea = $textArea.get(0);
- text = $textArea.val();
- selected = this.selectedText(text, textArea);
- $textArea.focus();
- return this.insertText(textArea, text, tag, blockTag, selected, wrap);
- };
- gl.text.init = function(form) {
- var self;
- self = this;
- return $('.js-md', form).off('click').on('click', function() {
- var $this;
- $this = $(this);
- return self.updateText($this.closest('.md-area').find('textarea'), $this.data('md-tag'), $this.data('md-block'), !$this.data('md-prepend'));
- });
- };
- gl.text.removeListeners = function(form) {
- return $('.js-md', form).off();
- };
- gl.text.humanize = function(string) {
- return string.charAt(0).toUpperCase() + string.replace(/_/g, ' ').slice(1);
- };
- gl.text.pluralize = function(str, count) {
- return str + (count > 1 || count === 0 ? 's' : '');
- };
- 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(window);
+ return textArea.setSelectionRange(pos, pos);
+ }
+};
+gl.text.updateText = function(textArea, tag, blockTag, wrap) {
+ var $textArea, selected, text;
+ $textArea = $(textArea);
+ textArea = $textArea.get(0);
+ text = $textArea.val();
+ selected = this.selectedText(text, textArea);
+ $textArea.focus();
+ return this.insertText(textArea, text, tag, blockTag, selected, wrap);
+};
+gl.text.init = function(form) {
+ var self;
+ self = this;
+ return $('.js-md', form).off('click').on('click', function() {
+ var $this;
+ $this = $(this);
+ return self.updateText($this.closest('.md-area').find('textarea'), $this.data('md-tag'), $this.data('md-block'), !$this.data('md-prepend'));
+ });
+};
+gl.text.removeListeners = function(form) {
+ return $('.js-md', form).off();
+};
+gl.text.humanize = function(string) {
+ return string.charAt(0).toUpperCase() + string.replace(/_/g, ' ').slice(1);
+};
+gl.text.pluralize = function(str, count) {
+ return str + (count > 1 || count === 0 ? 's' : '');
+};
+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();
+};
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
index 09c4261b318..b9d2fc25c39 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -1,93 +1,90 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, one-var, one-var-declaration-per-line, no-void, guard-for-in, no-restricted-syntax, prefer-template, quotes, max-len */
-(function() {
- (function(w) {
- var base;
- if (w.gl == null) {
- w.gl = {};
+var base;
+var w = window;
+if (w.gl == null) {
+ w.gl = {};
+}
+if ((base = w.gl).utils == null) {
+ base.utils = {};
+}
+// Returns an array containing the value(s) of the
+// of the key passed as an argument
+w.gl.utils.getParameterValues = function(sParam) {
+ var i, sPageURL, sParameterName, sURLVariables, values;
+ sPageURL = decodeURIComponent(window.location.search.substring(1));
+ sURLVariables = sPageURL.split('&');
+ sParameterName = void 0;
+ values = [];
+ i = 0;
+ while (i < sURLVariables.length) {
+ sParameterName = sURLVariables[i].split('=');
+ if (sParameterName[0] === sParam) {
+ values.push(sParameterName[1].replace(/\+/g, ' '));
}
- if ((base = w.gl).utils == null) {
- base.utils = {};
+ i += 1;
+ }
+ return values;
+};
+// @param {Object} params - url keys and value to merge
+// @param {String} url
+w.gl.utils.mergeUrlParams = function(params, url) {
+ var lastChar, newUrl, paramName, paramValue, pattern;
+ newUrl = decodeURIComponent(url);
+ for (paramName in params) {
+ paramValue = params[paramName];
+ pattern = new RegExp("\\b(" + paramName + "=).*?(&|$)");
+ if (paramValue == null) {
+ newUrl = newUrl.replace(pattern, '');
+ } else if (url.search(pattern) !== -1) {
+ newUrl = newUrl.replace(pattern, "$1" + paramValue + "$2");
+ } else {
+ newUrl = "" + newUrl + (newUrl.indexOf('?') > 0 ? '&' : '?') + paramName + "=" + paramValue;
}
- // Returns an array containing the value(s) of the
- // of the key passed as an argument
- w.gl.utils.getParameterValues = function(sParam) {
- var i, sPageURL, sParameterName, sURLVariables, values;
- sPageURL = decodeURIComponent(window.location.search.substring(1));
- sURLVariables = sPageURL.split('&');
- sParameterName = void 0;
- values = [];
- i = 0;
- while (i < sURLVariables.length) {
- sParameterName = sURLVariables[i].split('=');
- if (sParameterName[0] === sParam) {
- values.push(sParameterName[1].replace(/\+/g, ' '));
- }
- i += 1;
+ }
+ // Remove a trailing ampersand
+ lastChar = newUrl[newUrl.length - 1];
+ if (lastChar === '&') {
+ newUrl = newUrl.slice(0, -1);
+ }
+ return newUrl;
+};
+// removes parameter query string from url. returns the modified url
+w.gl.utils.removeParamQueryString = function(url, param) {
+ var urlVariables, variables;
+ url = decodeURIComponent(url);
+ urlVariables = url.split('&');
+ return ((function() {
+ var j, len, results;
+ results = [];
+ for (j = 0, len = urlVariables.length; j < len; j += 1) {
+ variables = urlVariables[j];
+ if (variables.indexOf(param) === -1) {
+ results.push(variables);
}
- return values;
- };
- // @param {Object} params - url keys and value to merge
- // @param {String} url
- w.gl.utils.mergeUrlParams = function(params, url) {
- var lastChar, newUrl, paramName, paramValue, pattern;
- newUrl = decodeURIComponent(url);
- for (paramName in params) {
- paramValue = params[paramName];
- pattern = new RegExp("\\b(" + paramName + "=).*?(&|$)");
- if (paramValue == null) {
- newUrl = newUrl.replace(pattern, '');
- } else if (url.search(pattern) !== -1) {
- newUrl = newUrl.replace(pattern, "$1" + paramValue + "$2");
- } else {
- newUrl = "" + newUrl + (newUrl.indexOf('?') > 0 ? '&' : '?') + paramName + "=" + paramValue;
- }
- }
- // Remove a trailing ampersand
- lastChar = newUrl[newUrl.length - 1];
- if (lastChar === '&') {
- newUrl = newUrl.slice(0, -1);
- }
- return newUrl;
- };
- // removes parameter query string from url. returns the modified url
- w.gl.utils.removeParamQueryString = function(url, param) {
- var urlVariables, variables;
- url = decodeURIComponent(url);
- urlVariables = url.split('&');
- return ((function() {
- var j, len, results;
- results = [];
- for (j = 0, len = urlVariables.length; j < len; j += 1) {
- variables = urlVariables[j];
- if (variables.indexOf(param) === -1) {
- results.push(variables);
- }
- }
- return results;
- })()).join('&');
- };
- w.gl.utils.removeParams = (params) => {
- const url = new URL(window.location.href);
- params.forEach((param) => {
- url.search = w.gl.utils.removeParamQueryString(url.search, param);
- });
- return url.href;
- };
- w.gl.utils.getLocationHash = function(url) {
- var hashIndex;
- if (typeof url === 'undefined') {
- // Note: We can't use window.location.hash here because it's
- // not consistent across browsers - Firefox will pre-decode it
- url = window.location.href;
- }
- hashIndex = url.indexOf('#');
- return hashIndex === -1 ? null : url.substring(hashIndex + 1);
- };
+ }
+ return results;
+ })()).join('&');
+};
+w.gl.utils.removeParams = (params) => {
+ const url = new URL(window.location.href);
+ params.forEach((param) => {
+ url.search = w.gl.utils.removeParamQueryString(url.search, param);
+ });
+ return url.href;
+};
+w.gl.utils.getLocationHash = function(url) {
+ var hashIndex;
+ if (typeof url === 'undefined') {
+ // Note: We can't use window.location.hash here because it's
+ // not consistent across browsers - Firefox will pre-decode it
+ url = window.location.href;
+ }
+ hashIndex = url.indexOf('#');
+ return hashIndex === -1 ? null : url.substring(hashIndex + 1);
+};
- w.gl.utils.refreshCurrentPage = () => gl.utils.visitUrl(document.location.href);
+w.gl.utils.refreshCurrentPage = () => gl.utils.visitUrl(document.location.href);
- w.gl.utils.visitUrl = (url) => {
- document.location.href = url;
- };
- })(window);
-}).call(window);
+w.gl.utils.visitUrl = (url) => {
+ document.location.href = url;
+};
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 3f92d4ee6cf..c50ec24c818 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -37,14 +37,7 @@ import './shortcuts_issuable';
import './shortcuts_network';
// behaviors
-import './behaviors/autosize';
-import './behaviors/details_behavior';
-import './behaviors/quick_submit';
-import './behaviors/requires_input';
-import './behaviors/toggler_behavior';
-import './behaviors/bind_in_out';
-import { installGlEmojiElement } from './behaviors/gl_emoji';
-installGlEmojiElement();
+import './behaviors/';
// blob
import './blob/create_branch_dropdown';
diff --git a/app/assets/javascripts/merged_buttons.js b/app/assets/javascripts/merged_buttons.js
index 9548a98f499..7b0997c6520 100644
--- a/app/assets/javascripts/merged_buttons.js
+++ b/app/assets/javascripts/merged_buttons.js
@@ -1,11 +1,13 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, max-len */
-(function() {
- var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
+import '~/lib/utils/url_utility';
+(function() {
this.MergedButtons = (function() {
function MergedButtons() {
- this.removeSourceBranch = bind(this.removeSourceBranch, this);
+ this.removeSourceBranch = this.removeSourceBranch.bind(this);
+ this.removeBranchSuccess = this.removeBranchSuccess.bind(this);
+ this.removeBranchError = this.removeBranchError.bind(this);
this.$removeBranchWidget = $('.remove_source_branch_widget');
this.$removeBranchProgress = $('.remove_source_branch_in_progress');
this.$removeBranchFailed = $('.remove_source_branch_widget.failed');
@@ -22,7 +24,7 @@
MergedButtons.prototype.initEventListeners = function() {
$(document).on('click', '.remove_source_branch', this.removeSourceBranch);
$(document).on('ajax:success', '.remove_source_branch', this.removeBranchSuccess);
- return $(document).on('ajax:error', '.remove_source_branch', this.removeBranchError);
+ $(document).on('ajax:error', '.remove_source_branch', this.removeBranchError);
};
MergedButtons.prototype.removeSourceBranch = function() {
@@ -31,7 +33,7 @@
};
MergedButtons.prototype.removeBranchSuccess = function() {
- return location.reload();
+ gl.utils.refreshCurrentPage();
};
MergedButtons.prototype.removeBranchError = function() {
diff --git a/app/assets/javascripts/shortcuts_wiki.js b/app/assets/javascripts/shortcuts_wiki.js
new file mode 100644
index 00000000000..8a075062a48
--- /dev/null
+++ b/app/assets/javascripts/shortcuts_wiki.js
@@ -0,0 +1,16 @@
+/* eslint-disable class-methods-use-this */
+/* global Mousetrap */
+/* global ShortcutsNavigation */
+
+import findAndFollowLink from './shortcuts_dashboard_navigation';
+
+export default class ShortcutsWiki extends ShortcutsNavigation {
+ constructor() {
+ super();
+ Mousetrap.bind('e', this.editWiki);
+ }
+
+ editWiki() {
+ findAndFollowLink('.js-wiki-edit');
+ }
+}
diff --git a/app/assets/javascripts/user_callout.js b/app/assets/javascripts/user_callout.js
index fa078b48bf8..b9d57cbcad4 100644
--- a/app/assets/javascripts/user_callout.js
+++ b/app/assets/javascripts/user_callout.js
@@ -18,7 +18,7 @@ export default class UserCallout {
dismissCallout(e) {
const $currentTarget = $(e.currentTarget);
- Cookies.set(USER_CALLOUT_COOKIE, 'true');
+ Cookies.set(USER_CALLOUT_COOKIE, 'true', { expires: 365 });
if ($currentTarget.hasClass('close')) {
this.userCalloutBody.remove();
diff --git a/app/assets/javascripts/vue_pipelines_index/components/async_button.js b/app/assets/javascripts/vue_pipelines_index/components/async_button.vue
index 58b8db4d519..11da6e908b7 100644
--- a/app/assets/javascripts/vue_pipelines_index/components/async_button.js
+++ b/app/assets/javascripts/vue_pipelines_index/components/async_button.vue
@@ -1,3 +1,4 @@
+<script>
/* eslint-disable no-new, no-alert */
/* global Flash */
import '~/flash';
@@ -65,29 +66,31 @@ export default {
this.isLoading = true;
this.service.postAction(this.endpoint)
- .then(() => {
- this.isLoading = false;
- eventHub.$emit('refreshPipelines');
- })
- .catch(() => {
- this.isLoading = false;
- new Flash('An error occured while making the request.');
- });
+ .then(() => {
+ this.isLoading = false;
+ eventHub.$emit('refreshPipelines');
+ })
+ .catch(() => {
+ this.isLoading = false;
+ new Flash('An error occured while making the request.');
+ });
},
},
-
- template: `
- <button
- type="button"
- @click="onClick"
- :class="buttonClass"
- :title="title"
- :aria-label="title"
- data-container="body"
- data-placement="top"
- :disabled="isLoading">
- <i :class="iconClass" aria-hidden="true"/>
- <i class="fa fa-spinner fa-spin" aria-hidden="true" v-if="isLoading" />
- </button>
- `,
};
+</script>
+
+<template>
+ <button
+ type="button"
+ @click="onClick"
+ :class="buttonClass"
+ :title="title"
+ :aria-label="title"
+ data-container="body"
+ data-placement="top"
+ :disabled="isLoading"
+ >
+ <i :class="iconClass" aria-hidden="true"></i>
+ <i class="fa fa-spinner fa-spin" aria-hidden="true" v-if="isLoading"></i>
+ </button>
+</template>
diff --git a/app/assets/javascripts/vue_pipelines_index/components/empty_state.js b/app/assets/javascripts/vue_pipelines_index/components/empty_state.js
deleted file mode 100644
index 56b4858f4b4..00000000000
--- a/app/assets/javascripts/vue_pipelines_index/components/empty_state.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import pipelinesEmptyStateSVG from 'empty_states/icons/_pipelines_empty.svg';
-
-export default {
- props: {
- helpPagePath: {
- type: String,
- required: true,
- },
- },
-
- template: `
- <div class="row empty-state">
- <div class="col-xs-12">
- <div class="svg-content">
- ${pipelinesEmptyStateSVG}
- </div>
- </div>
-
- <div class="col-xs-12 text-center">
- <div class="text-content">
- <h4>Build with confidence</h4>
- <p>
- Continous Integration can help catch bugs by running your tests automatically,
- while Continuous Deployment can help you deliver code to your product environment.
- </p>
- <a :href="helpPagePath" class="btn btn-info">
- Get started with Pipelines
- </a>
- </div>
- </div>
- </div>
- `,
-};
diff --git a/app/assets/javascripts/vue_pipelines_index/components/empty_state.vue b/app/assets/javascripts/vue_pipelines_index/components/empty_state.vue
new file mode 100644
index 00000000000..ba158bc4a1e
--- /dev/null
+++ b/app/assets/javascripts/vue_pipelines_index/components/empty_state.vue
@@ -0,0 +1,34 @@
+<script>
+import pipelinesEmptyStateSVG from 'empty_states/icons/_pipelines_empty.svg';
+
+export default {
+ props: {
+ helpPagePath: {
+ type: String,
+ required: true,
+ },
+ },
+ data: () => ({ pipelinesEmptyStateSVG }),
+};
+</script>
+
+<template>
+ <div class="row empty-state">
+ <div class="col-xs-12">
+ <div class="svg-content" v-html="pipelinesEmptyStateSVG" />
+ </div>
+
+ <div class="col-xs-12 text-center">
+ <div class="text-content">
+ <h4>Build with confidence</h4>
+ <p>
+ Continous Integration can help catch bugs by running your tests automatically,
+ while Continuous Deployment can help you deliver code to your product environment.
+ </p>
+ <a :href="helpPagePath" class="btn btn-info">
+ Get started with Pipelines
+ </a>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_pipelines_index/components/error_state.js b/app/assets/javascripts/vue_pipelines_index/components/error_state.js
deleted file mode 100644
index e5d228bddf8..00000000000
--- a/app/assets/javascripts/vue_pipelines_index/components/error_state.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import pipelinesErrorStateSVG from 'empty_states/icons/_pipelines_failed.svg';
-
-export default {
- template: `
- <div class="row empty-state js-pipelines-error-state">
- <div class="col-xs-12">
- <div class="svg-content">
- ${pipelinesErrorStateSVG}
- </div>
- </div>
-
- <div class="col-xs-12 text-center">
- <div class="text-content">
- <h4>The API failed to fetch the pipelines.</h4>
- </div>
- </div>
- </div>
- `,
-};
diff --git a/app/assets/javascripts/vue_pipelines_index/components/error_state.vue b/app/assets/javascripts/vue_pipelines_index/components/error_state.vue
new file mode 100644
index 00000000000..90cee68163e
--- /dev/null
+++ b/app/assets/javascripts/vue_pipelines_index/components/error_state.vue
@@ -0,0 +1,21 @@
+<script>
+import pipelinesErrorStateSVG from 'empty_states/icons/_pipelines_failed.svg';
+
+export default {
+ data: () => ({ pipelinesErrorStateSVG }),
+};
+</script>
+
+<template>
+ <div class="row empty-state js-pipelines-error-state">
+ <div class="col-xs-12">
+ <div class="svg-content" v-html="pipelinesErrorStateSVG" />
+ </div>
+
+ <div class="col-xs-12 text-center">
+ <div class="text-content">
+ <h4>The API failed to fetch the pipelines.</h4>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js b/app/assets/javascripts/vue_pipelines_index/pipelines.js
index 5575aa72d5e..6eea4812f33 100644
--- a/app/assets/javascripts/vue_pipelines_index/pipelines.js
+++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js
@@ -4,8 +4,8 @@ import PipelinesService from './services/pipelines_service';
import eventHub from './event_hub';
import PipelinesTableComponent from '../vue_shared/components/pipelines_table';
import TablePaginationComponent from '../vue_shared/components/table_pagination';
-import EmptyState from './components/empty_state';
-import ErrorState from './components/error_state';
+import EmptyState from './components/empty_state.vue';
+import ErrorState from './components/error_state.vue';
import NavigationTabs from './components/navigation_tabs';
import NavigationControls from './components/nav_controls';
import Poll from '../lib/utils/poll';
diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js
index f5b3cb9214e..8ebe12cb1c5 100644
--- a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js
+++ b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js
@@ -1,6 +1,6 @@
/* eslint-disable no-param-reassign */
-import AsyncButtonComponent from '../../vue_pipelines_index/components/async_button';
+import AsyncButtonComponent from '../../vue_pipelines_index/components/async_button.vue';
import PipelinesActionsComponent from '../../vue_pipelines_index/components/pipelines_actions';
import PipelinesArtifactsComponent from '../../vue_pipelines_index/components/pipelines_artifacts';
import PipelinesStatusComponent from '../../vue_pipelines_index/components/status';
diff --git a/app/assets/stylesheets/framework/awards.scss b/app/assets/stylesheets/framework/awards.scss
index b849cc2d853..f614f262316 100644
--- a/app/assets/stylesheets/framework/awards.scss
+++ b/app/assets/stylesheets/framework/awards.scss
@@ -38,6 +38,15 @@
height: 300px;
overflow-y: scroll;
}
+
+ .disabled {
+ cursor: default;
+ opacity: 0.5;
+
+ &:hover {
+ transform: none;
+ }
+ }
}
.emoji-search {
@@ -154,6 +163,17 @@
}
}
+ &.user-authored {
+ cursor: default;
+ opacity: 0.65;
+
+ &:hover,
+ &:active {
+ background-color: $white-light;
+ border-color: $border-color;
+ }
+ }
+
&.btn {
&:focus {
outline: 0;
diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss
index 9a0f7a14e57..759401a7806 100644
--- a/app/assets/stylesheets/framework/calendar.scss
+++ b/app/assets/stylesheets/framework/calendar.scss
@@ -5,7 +5,7 @@
direction: rtl;
@media (min-width: $screen-sm-min) and (max-width: $screen-md-max) {
- overflow-x: scroll;
+ overflow-x: auto;
}
}
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index 12465d4a70b..11d44df4867 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -82,7 +82,7 @@
.input-token:last-child {
flex: 1;
-webkit-flex: 1;
- max-width: initial;
+ max-width: inherit;
}
}
@@ -246,17 +246,17 @@
}
}
-.filtered-search-history-dropdown-toggle-button {
+.filtered-search-history-dropdown-wrapper {
+ position: static;
display: flex;
- align-items: center;
+ flex-direction: column;
+}
+
+.filtered-search-history-dropdown-toggle-button {
+ flex: 1;
width: auto;
- height: 100%;
- padding-top: 0;
- padding-left: 0.75em;
- padding-bottom: 0;
- padding-right: 0.5em;
+ padding-right: 10px;
- background-color: transparent;
border-radius: 0;
border-top: 0;
border-left: 0;
@@ -264,6 +264,7 @@
border-right: 1px solid $border-color;
color: $gl-text-color-secondary;
+ line-height: 1;
transition: color 0.1s linear;
@@ -275,24 +276,21 @@
}
.dropdown-toggle-text {
+ display: inline-block;
color: inherit;
.fa {
+ vertical-align: middle;
color: inherit;
}
}
.fa {
- position: initial;
+ position: static;
}
}
-.filtered-search-history-dropdown-wrapper {
- position: initial;
- flex-shrink: 0;
-}
-
.filtered-search-history-dropdown {
width: 40%;
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index abb092623c0..2071d4dcfce 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -155,7 +155,7 @@ header {
.header-logo {
display: inline-block;
- margin: 0 7px 0 2px;
+ margin: 0 12px 0 2px;
position: relative;
top: 10px;
transition-duration: .3s;
@@ -186,7 +186,7 @@ header {
display: flex;
align-items: flex-start;
flex: 1 1 auto;
- padding-top: (($header-height - 19) / 2);
+ padding-top: 14px;
overflow: hidden;
}
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index c241816788b..664539e93e1 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -158,6 +158,7 @@
li.task-list-item {
list-style-type: none;
position: relative;
+ min-height: 22px;
padding-left: 28px;
margin-left: 0 !important;
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 712eb7caf33..20ef9a774e4 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -26,6 +26,7 @@ $gray-dark: darken($gray-light, $darken-dark-factor);
$gray-darker: #eee;
$gray-darkest: #c4c4c4;
+$green-25: #f6fcf8;
$green-50: #e4f5eb;
$green-100: #bae6cc;
$green-200: #8dd5aa;
@@ -37,6 +38,7 @@ $green-700: #12753a;
$green-800: #0e5a2d;
$green-900: #0a4020;
+$blue-25: #f6fafd;
$blue-50: #e4eff9;
$blue-100: #bcd7f1;
$blue-200: #8fbce8;
@@ -48,6 +50,7 @@ $blue-700: #17599c;
$blue-800: #134a81;
$blue-900: #0f3b66;
+$orange-25: #fffcf8;
$orange-50: #fff2e1;
$orange-100: #fedfb3;
$orange-200: #feca81;
@@ -59,6 +62,7 @@ $orange-700: #c26700;
$orange-800: #a35100;
$orange-900: #853b00;
+$red-25: #fef7f6;
$red-50: #fbe7e4;
$red-100: #f4c4bc;
$red-200: #ed9d90;
@@ -147,7 +151,7 @@ $gl-sidebar-padding: 22px;
/*
* Misc
*/
-$row-hover: lighten($blue-50, 2%);
+$row-hover: $blue-25;
$row-hover-border: $blue-100;
$progress-color: #c0392b;
$header-height: 50px;
@@ -223,18 +227,18 @@ $gl-btn-active-gradient: inset 0 2px 3px $gl-btn-active-background;
/*
* Commit Diff Colors
*/
-$added: $green-300;
-$deleted: $red-300;
-$line-added: $green-50;
-$line-added-dark: $green-100;
-$line-removed: $red-50;
-$line-removed-dark: $red-100;
-$line-number-old: lighten($red-100, 5%);
-$line-number-new: lighten($green-100, 5%);
-$line-number-select: lighten($orange-100, 5%);
-$line-target-blue: $blue-50;
-$line-select-yellow: $orange-50;
-$line-select-yellow-dark: $orange-100;
+$added: #63c363;
+$deleted: #f77;
+$line-added: #ecfdf0;
+$line-added-dark: #c7f0d2;
+$line-removed: #fbe9eb;
+$line-removed-dark: #fac5cd;
+$line-number-old: #f9d7dc;
+$line-number-new: #ddfbe6;
+$line-number-select: #fbf2da;
+$line-target-blue: #f6faff;
+$line-select-yellow: #fcf8e7;
+$line-select-yellow-dark: #f0e2bd;
$dark-diff-match-bg: rgba(255, 255, 255, 0.3);
$dark-diff-match-color: rgba(255, 255, 255, 0.1);
$file-mode-changed: #777;
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 03fddaeb163..411f1c4442b 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -39,7 +39,7 @@
overflow-y: hidden;
font-size: 12px;
- .fa-refresh {
+ .fa-spinner {
font-size: 24px;
margin-left: 20px;
}
@@ -61,8 +61,9 @@
.truncated-info {
text-align: center;
border-bottom: 1px solid;
- background-color: $black-transparent;
+ background-color: $black;
height: 45px;
+ padding: 15px;
&.affix {
top: 0;
@@ -87,6 +88,16 @@
right: 5px;
left: 5px;
}
+
+ .truncated-info-size {
+ margin: 0 5px;
+ }
+
+ .raw-link {
+ color: inherit;
+ margin-left: 5px;
+ text-decoration: underline;
+ }
}
}
@@ -219,7 +230,7 @@
font-size: 12px;
position: relative;
- .fa-refresh {
+ .fa-spinner {
font-size: 24px;
}
@@ -366,7 +377,7 @@
background-color: $row-hover;
}
- .fa-refresh {
+ .fa-spinner {
font-size: 13px;
margin-left: 3px;
}
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index 0dad91ba128..9e3142c8aa3 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -135,7 +135,7 @@
.text-expander {
display: inline-block;
- background: $gray-light;
+ background: $white-light;
color: $gl-text-color-secondary;
padding: 0 5px;
cursor: pointer;
@@ -146,6 +146,11 @@
line-height: $gl-font-size;
outline: none;
+ &.open {
+ background: $gray-light;
+ box-shadow: inset 0 0 2px rgba($black, 0.2);
+ }
+
&:hover {
background-color: darken($gray-light, 10%);
text-decoration: none;
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 1aa1079903c..1b4694377b3 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -106,6 +106,10 @@
span {
white-space: pre-wrap;
}
+
+ .line {
+ word-wrap: break-word;
+ }
}
}
diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss
index 79da490675a..5b723f7c722 100644
--- a/app/assets/stylesheets/pages/events.scss
+++ b/app/assets/stylesheets/pages/events.scss
@@ -10,10 +10,14 @@
position: relative;
&.event-inline {
- .profile-icon {
+ .system-note-image {
top: 20px;
}
+ .user-avatar {
+ top: 14px;
+ }
+
.event-title,
.event-item-timestamp {
line-height: 40px;
@@ -24,7 +28,7 @@
color: $gl-text-color;
}
- .profile-icon {
+ .system-note-image {
position: absolute;
left: 0;
top: 14px;
@@ -35,15 +39,18 @@
fill: $gl-text-color-secondary;
}
- &.open-icon svg {
- fill: $green-300;
+ &.opened-icon,
+ &.created-icon {
+ svg {
+ fill: $green-300;
+ }
}
&.closed-icon svg {
fill: $red-300;
}
- &.fork-icon svg {
+ &.accepted-icon svg {
fill: $blue-300;
}
}
@@ -128,8 +135,7 @@
li {
&.commit {
background: transparent;
- padding: 3px;
- padding-left: 0;
+ padding: 0;
border: none;
.commit-row-title {
@@ -183,7 +189,7 @@
max-width: 100%;
}
- .profile-icon {
+ .system-note-image {
display: none;
}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 2f946ab2f59..6a419384a34 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -523,11 +523,12 @@
}
.content-block {
- border-top: 1px solid $border-color;
padding: $gl-padding-top $gl-padding;
}
.comments-disabled-notif {
+ line-height: 28px;
+
.btn {
margin-left: 5px;
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 94ea4c5c8c6..2ea2ff8362b 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -18,12 +18,12 @@ ul.notes {
float: left;
svg {
- width: 18px;
- height: 18px;
+ width: 16px;
+ height: 16px;
fill: $gray-darkest;
position: absolute;
- left: 30px;
- top: 15px;
+ left: 0;
+ top: 16px;
}
}
@@ -123,6 +123,9 @@ ul.notes {
}
.note-emoji-button {
+ position: relative;
+ line-height: 1;
+
.fa-spinner {
display: none;
}
@@ -144,6 +147,10 @@ ul.notes {
padding: 0;
clear: both;
+ @media (min-width: $screen-sm-min) {
+ margin-left: 65px;
+ }
+
&.timeline-entry::after {
clear: none;
}
@@ -172,6 +179,10 @@ ul.notes {
.timeline-content {
padding: 14px 10px;
+
+ @media (min-width: $screen-sm-min) {
+ margin-left: 20px;
+ }
}
.note-header {
@@ -344,6 +355,15 @@ ul.notes {
font-size: 14px;
}
+.note-header {
+ display: flex;
+ justify-content: space-between;
+}
+
+.note-header-info {
+ min-width: 0;
+}
+
.note-headline-light {
display: inline;
@@ -363,21 +383,27 @@ ul.notes {
}
}
+.note-headline-meta {
+ display: inline-block;
+ white-space: nowrap;
+}
+
/**
* Actions for Discussions/Notes
*/
-.discussion-actions,
-.note-actions {
+.discussion-actions {
float: right;
margin-left: 10px;
color: $gray-darkest;
}
.note-actions {
- position: absolute;
- right: 0;
- top: 0;
+ flex-shrink: 0;
+ // For PhantomJS that does not support flex
+ float: right;
+ margin-left: 10px;
+ color: $gray-darkest;
.note-action-button {
margin-left: 8px;
@@ -420,7 +446,8 @@ ul.notes {
.award-control-icon-positive,
.award-control-icon-super-positive {
position: absolute;
- margin-left: -20px;
+ top: 0;
+ left: 0;
opacity: 0;
}
@@ -619,7 +646,6 @@ ul.notes {
}
&:not(.is-disabled):hover,
- &:not(.is-disabled):focus,
&.is-active {
color: $gl-text-green;
@@ -633,6 +659,11 @@ ul.notes {
height: 15px;
width: 15px;
}
+
+ .loading {
+ margin: 0;
+ height: auto;
+ }
}
.discussion-next-btn {
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 8c6dd392865..fe084eb9397 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -289,8 +289,12 @@ table.u2f-registrations {
margin: 0 auto;
.bordered-box {
- border: 1px solid $border-color;
+ border: 1px solid $blue-300;
border-radius: $border-radius-default;
+ background-color: $blue-25;
+ position: relative;
+ display: flex;
+ justify-content: center;
}
.landing {
@@ -298,28 +302,59 @@ table.u2f-registrations {
margin-bottom: $gl-padding;
.close {
- margin-right: 20px;
- }
+ position: absolute;
+ right: 20px;
+ opacity: 1;
+
+ .dismiss-icon {
+ float: right;
+ cursor: pointer;
+ color: $blue-300;
+ }
- .dismiss-icon {
- float: right;
- cursor: pointer;
- color: $cycle-analytics-dismiss-icon-color;
+ &:hover {
+ background-color: transparent;
+ border: 0;
+
+ .dismiss-icon {
+ color: $blue-400;
+ }
+ }
}
.svg-container {
- text-align: center;
+ margin-right: 30px;
+ display: inline-block;
svg {
- width: 136px;
- height: 136px;
+ height: 110px;
+ vertical-align: top;
}
}
+
+ .user-callout-copy {
+ display: inline-block;
+ vertical-align: top;
+ }
}
@media(max-width: $screen-xs-max) {
- .inner-content {
- padding-left: 30px;
+ text-align: center;
+
+ .bordered-box {
+ display: block;
+ }
+
+ .landing {
+ .svg-container,
+ .user-callout-copy {
+ margin: 0;
+ display: block;
+
+ svg {
+ height: 75px;
+ }
+ }
}
}
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 717ebb44a23..a9e2a034790 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -929,27 +929,23 @@ pre.light-well {
}
.variable-key {
- width: 300px;
- max-width: 300px;
+ max-width: 120px;
overflow: hidden;
word-wrap: break-word;
-
- // override bootstrap
- white-space: normal!important;
-
- @media (max-width: $screen-sm-max) {
- width: 150px;
- max-width: 150px;
- }
+ white-space: nowrap;
+ text-overflow: ellipsis;
}
.variable-value {
- @media(max-width: $screen-xs-max) {
- width: 150px;
- max-width: 150px;
- overflow: hidden;
- word-wrap: break-word;
- }
+ max-width: 150px;
+ overflow: hidden;
+ word-wrap: break-word;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ }
+
+ .variable-menu {
+ text-align: right;
}
}
diff --git a/app/controllers/admin/application_controller.rb b/app/controllers/admin/application_controller.rb
index cf795d977ce..a4648b33cfa 100644
--- a/app/controllers/admin/application_controller.rb
+++ b/app/controllers/admin/application_controller.rb
@@ -6,6 +6,6 @@ class Admin::ApplicationController < ApplicationController
layout 'admin'
def authenticate_admin!
- render_404 unless current_user.is_admin?
+ render_404 unless current_user.admin?
end
end
diff --git a/app/controllers/admin/impersonations_controller.rb b/app/controllers/admin/impersonations_controller.rb
index 9433da02f64..8e7adc06584 100644
--- a/app/controllers/admin/impersonations_controller.rb
+++ b/app/controllers/admin/impersonations_controller.rb
@@ -21,6 +21,6 @@ class Admin::ImpersonationsController < Admin::ApplicationController
end
def authenticate_impersonator!
- render_404 unless impersonator && impersonator.is_admin? && !impersonator.blocked?
+ render_404 unless impersonator && impersonator.admin? && !impersonator.blocked?
end
end
diff --git a/app/controllers/admin/spam_logs_controller.rb b/app/controllers/admin/spam_logs_controller.rb
index 2abfa22712d..1d66955bb71 100644
--- a/app/controllers/admin/spam_logs_controller.rb
+++ b/app/controllers/admin/spam_logs_controller.rb
@@ -7,7 +7,7 @@ class Admin::SpamLogsController < Admin::ApplicationController
spam_log = SpamLog.find(params[:id])
if params[:remove_user]
- spam_log.remove_user
+ spam_log.remove_user(deleted_by: current_user)
redirect_to admin_spam_logs_path, notice: "User #{spam_log.user.username} was successfully removed."
else
spam_log.destroy
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index c6651254d70..008d2f5815f 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -61,7 +61,6 @@ class Projects::CompareController < Projects::ApplicationController
@environment = EnvironmentsFinder.new(@project, current_user, environment_params).execute.last
@diff_notes_disabled = true
- @grouped_diff_discussions = {}
end
end
diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index b668a9331e7..1e41f980f31 100644
--- a/app/controllers/projects/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -10,7 +10,7 @@ class Projects::HooksController < Projects::ApplicationController
@hook = @project.hooks.new(hook_params)
@hook.save
- unless @hook.valid?
+ unless @hook.valid?
@hooks = @project.hooks.select(&:persisted?)
flash[:alert] = @hook.errors.full_messages.join.html_safe
end
@@ -49,7 +49,7 @@ class Projects::HooksController < Projects::ApplicationController
def hook_params
params.require(:hook).permit(
- :build_events,
+ :job_events,
:pipeline_events,
:enable_ssl_verification,
:issues_events,
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 5c1f7e69ee8..09dc8b38229 100755
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -16,7 +16,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
before_action :define_show_vars, only: [:show, :diffs, :commits, :conflicts, :conflict_for_path, :builds, :pipelines]
before_action :define_widget_vars, only: [:merge, :cancel_merge_when_pipeline_succeeds, :merge_check]
before_action :define_commit_vars, only: [:diffs]
- before_action :define_diff_comment_vars, only: [:diffs]
before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds, :conflicts, :conflict_for_path, :pipelines]
before_action :close_merge_request_without_source_project, only: [:show, :diffs, :commits, :builds, :pipelines]
before_action :apply_diff_view_cookie!, only: [:new_diffs]
@@ -39,7 +38,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@collection_type = "MergeRequest"
@merge_requests = merge_requests_collection
@merge_requests = @merge_requests.page(params[:page])
- @merge_requests = @merge_requests.includes(merge_request_diff: :merge_request)
+ @merge_requests = @merge_requests.preload(merge_request_diff: :merge_request)
@issuable_meta_data = issuable_meta_data(@merge_requests, @collection_type)
if @merge_requests.out_of_range? && @merge_requests.total_pages != 0
@@ -101,34 +100,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
respond_to do |format|
format.html { define_discussion_vars }
format.json do
- @merge_request_diff =
- if params[:diff_id]
- @merge_request.merge_request_diffs.viewable.find(params[:diff_id])
- else
- @merge_request.merge_request_diff
- end
-
- @merge_request_diffs = @merge_request.merge_request_diffs.viewable.select_without_diff
- @comparable_diffs = @merge_request_diffs.select { |diff| diff.id < @merge_request_diff.id }
-
- if params[:start_sha].present?
- @start_sha = params[:start_sha]
- @start_version = @comparable_diffs.find { |diff| diff.head_commit_sha == @start_sha }
-
- unless @start_version
- @start_sha = @merge_request_diff.head_commit_sha
- @start_version = @merge_request_diff
- end
- end
+ define_diff_vars
+ define_diff_comment_vars
@environment = @merge_request.environments_for(current_user).last
- if @start_sha
- compared_diff_version
- else
- original_diff_version
- end
-
render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") }
end
end
@@ -140,16 +116,17 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def diff_for_path
if params[:id]
merge_request
+ define_diff_vars
define_diff_comment_vars
else
build_merge_request
+ @diffs = @merge_request.diffs(diff_options)
@diff_notes_disabled = true
- @grouped_diff_discussions = {}
end
define_commit_vars
- render_diff_for_path(@merge_request.diffs(diff_options))
+ render_diff_for_path(@diffs)
end
def commits
@@ -586,15 +563,46 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@base_commit = @merge_request.diff_base_commit || @merge_request.likely_diff_base_commit
end
+ def define_diff_vars
+ @merge_request_diff =
+ if params[:diff_id]
+ @merge_request.merge_request_diffs.viewable.find(params[:diff_id])
+ else
+ @merge_request.merge_request_diff
+ end
+
+ @merge_request_diffs = @merge_request.merge_request_diffs.viewable.select_without_diff
+ @comparable_diffs = @merge_request_diffs.select { |diff| diff.id < @merge_request_diff.id }
+
+ if params[:start_sha].present?
+ @start_sha = params[:start_sha]
+ @start_version = @comparable_diffs.find { |diff| diff.head_commit_sha == @start_sha }
+
+ unless @start_version
+ @start_sha = @merge_request_diff.head_commit_sha
+ @start_version = @merge_request_diff
+ end
+ end
+
+ @diffs =
+ if @start_sha
+ @merge_request_diff.compare_with(@start_sha).diffs(diff_options)
+ else
+ @merge_request_diff.diffs(diff_options)
+ end
+ end
+
def define_diff_comment_vars
@new_diff_note_attrs = {
noteable_type: 'MergeRequest',
noteable_id: @merge_request.id
}
+ @diff_notes_disabled = !@merge_request_diff.latest? || @start_sha
+
@use_legacy_diff_notes = !@merge_request.has_complete_diff_refs?
- @grouped_diff_discussions = @merge_request.grouped_diff_discussions
+ @grouped_diff_discussions = @merge_request.grouped_diff_discussions(@merge_request_diff.diff_refs)
@notes = prepare_notes_for_rendering(@grouped_diff_discussions.values.flatten.flat_map(&:notes))
end
@@ -678,16 +686,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params.merge(diff_options: diff_options)).execute
end
- def compared_diff_version
- @diff_notes_disabled = true
- @diffs = @merge_request_diff.compare_with(@start_sha).diffs(diff_options)
- end
-
- def original_diff_version
- @diff_notes_disabled = !@merge_request_diff.latest?
- @diffs = @merge_request_diff.diffs(diff_options)
- end
-
def close_merge_request_without_source_project
if !@merge_request.source_project && @merge_request.open?
@merge_request.close
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 47f7e0b1b28..6807c37f972 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -345,7 +345,11 @@ class ProjectsController < Projects::ApplicationController
end
def project_view_files?
- current_user && current_user.project_view == 'files'
+ if current_user
+ current_user.project_view == 'files'
+ else
+ project_view_files_allowed?
+ end
end
# Override extract_ref from ExtractsPath, which returns the branch and file path
@@ -359,4 +363,8 @@ class ProjectsController < Projects::ApplicationController
def get_id
project.repository.root_ref
end
+
+ def project_view_files_allowed?
+ !project.empty_repo? && can?(current_user, :download_code, project)
+ end
end
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index 8109427a45f..3ca14dee33c 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -60,7 +60,7 @@ class RegistrationsController < Devise::RegistrationsController
end
def resource
- @resource ||= Users::CreateService.new(current_user, sign_up_params).build
+ @resource ||= Users::BuildService.new(current_user, sign_up_params).execute
end
def devise_mapping
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index b0ac1623fbe..4b3ab03a69c 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -34,7 +34,7 @@ module BlobHelper
if !on_top_of_branch?(project, ref)
button_tag 'Edit', class: "#{common_classes} disabled has-tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' }
# This condition applies to anonymous or users who can edit directly
- elsif !current_user || (current_user && can_edit_blob?(blob, project, ref))
+ elsif !current_user || (current_user && can_modify_blob?(blob, project, ref))
link_to 'Edit', edit_path(project, ref, path, options), class: "#{common_classes} btn-sm"
elsif current_user && can?(current_user, :fork_project, project)
button_tag 'Edit', class: "#{common_classes} js-edit-blob-link-fork-toggler"
@@ -52,7 +52,7 @@ module BlobHelper
button_tag label, class: "btn btn-#{btn_class} disabled has-tooltip", title: "You can only #{action} files when you are on a branch", data: { container: 'body' }
elsif blob.lfs_pointer?
button_tag label, class: "btn btn-#{btn_class} disabled has-tooltip", title: "It is not possible to #{action} files that are stored in LFS using the web interface", data: { container: 'body' }
- elsif can_edit_blob?(blob, project, ref)
+ elsif can_modify_blob?(blob, project, ref)
button_tag label, class: "btn btn-#{btn_class}", 'data-target' => "#modal-#{modal_type}-blob", 'data-toggle' => 'modal'
elsif can?(current_user, :fork_project, project)
continue_params = {
@@ -90,7 +90,7 @@ module BlobHelper
)
end
- def can_edit_blob?(blob, project = @project, ref = @ref)
+ def can_modify_blob?(blob, project = @project, ref = @ref)
!blob.lfs_pointer? && can_edit_tree?(project, ref)
end
@@ -118,6 +118,10 @@ module BlobHelper
blob && blob.text? && !blob.lfs_pointer? && !blob.only_display_raw?
end
+ def blob_rendered_as_text?(blob)
+ blob_text_viewable?(blob) && blob.to_partial_path(@project) == 'text'
+ end
+
def blob_size(blob)
if blob.lfs_pointer?
blob.lfs_size
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index 5e0886cc599..dc144906548 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -62,6 +62,8 @@ module DiffHelper
end
def parallel_diff_discussions(left, right, diff_file)
+ return unless @grouped_diff_discussions
+
discussions_left = discussions_right = nil
if left && (left.unchanged? || left.removed?)
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index fb872a13f74..5f5c76d3722 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -1,4 +1,15 @@
module EventsHelper
+ ICON_NAMES_BY_EVENT_TYPE = {
+ 'pushed to' => 'icon_commit',
+ 'pushed new' => 'icon_commit',
+ 'created' => 'icon_status_open',
+ 'opened' => 'icon_status_open',
+ 'closed' => 'icon_status_closed',
+ 'accepted' => 'icon_code_fork',
+ 'commented on' => 'icon_comment_o',
+ 'deleted' => 'icon_trash_o'
+ }.freeze
+
def link_to_author(event)
author = event.author
@@ -183,4 +194,21 @@ module EventsHelper
"event-inline"
end
end
+
+ def icon_for_event(note)
+ icon_name = ICON_NAMES_BY_EVENT_TYPE[note]
+ custom_icon(icon_name) if icon_name
+ end
+
+ def icon_for_profile_event(event)
+ if current_path?('users#show')
+ content_tag :div, class: "system-note-image #{event.action_name.parameterize}-icon" do
+ icon_for_event(event.action_name)
+ end
+ else
+ content_tag :div, class: 'system-note-image user-avatar' do
+ author_avatar(event, size: 32)
+ end
+ end
+ end
end
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 6978b0c89fd..82288f1da35 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -110,6 +110,14 @@ module IssuesHelper
end
end
+ def award_user_authored_class(award)
+ if award == 'thumbsdown' || award == 'thumbsup'
+ 'user-authored js-user-authored'
+ else
+ ''
+ end
+ end
+
def awards_sort(awards)
awards.sort_by do |award, notes|
if award == "thumbsup"
diff --git a/app/helpers/javascript_helper.rb b/app/helpers/javascript_helper.rb
index 68c09c922a6..d5e77c7e271 100644
--- a/app/helpers/javascript_helper.rb
+++ b/app/helpers/javascript_helper.rb
@@ -3,7 +3,8 @@ module JavascriptHelper
javascript_include_tag asset_path(js)
end
- def page_specific_javascript_bundle_tag(js)
- javascript_include_tag(*webpack_asset_paths(js))
+ # deprecated; use webpack_bundle_tag directly instead
+ def page_specific_javascript_bundle_tag(bundle)
+ webpack_bundle_tag(bundle)
end
end
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index 5f3d89cf6cb..eab0738a368 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -61,12 +61,23 @@ module NotesHelper
end
def discussion_diff_path(discussion)
- return unless discussion.diff_discussion?
+ if discussion.for_merge_request? && discussion.diff_discussion?
+ if discussion.active?
+ # Without a diff ID, the link always points to the latest diff version
+ diff_id = nil
+ elsif merge_request_diff = discussion.latest_merge_request_diff
+ diff_id = merge_request_diff.id
+ else
+ # If the discussion is not active, and we cannot find the latest
+ # merge request diff for this discussion, we return no path at all.
+ return
+ end
- if discussion.for_merge_request? && discussion.active?
- diffs_namespace_project_merge_request_path(discussion.project.namespace, discussion.project, discussion.noteable, anchor: discussion.line_code)
+ diffs_namespace_project_merge_request_path(discussion.project.namespace, discussion.project, discussion.noteable, diff_id: diff_id, anchor: discussion.line_code)
elsif discussion.for_commit?
- namespace_project_commit_path(discussion.project.namespace, discussion.project, discussion.noteable, anchor: discussion.line_code)
+ anchor = discussion.line_code if discussion.diff_discussion?
+
+ namespace_project_commit_path(discussion.project.namespace, discussion.project, discussion.noteable, anchor: anchor)
end
end
end
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index 243ef39ef61..de959f13713 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -63,6 +63,10 @@ module PreferencesHelper
end
def anonymous_project_view
- @project.empty_repo? || !can?(current_user, :download_code, @project) ? 'activity' : 'readme'
+ if !@project.empty_repo? && can?(current_user, :download_code, @project)
+ 'files'
+ else
+ 'activity'
+ end
end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 6b9e4267281..43669b6f356 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -24,7 +24,7 @@ module ProjectsHelper
return "(deleted)" unless author
- author_html = ""
+ author_html = ""
# Build avatar image tag
author_html << image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]} #{opts[:avatar_class] if opts[:avatar_class]}", alt: '') if opts[:avatar]
@@ -45,7 +45,7 @@ module ProjectsHelper
link_to(author_html, user_path(author), class: "author_link #{"#{opts[:extra_class]}" if opts[:extra_class]} #{"#{opts[:mobile_classes]}" if opts[:mobile_classes]}").html_safe
else
title = opts[:title].sub(":name", sanitize(author.name))
- link_to(author_html, user_path(author), class: "author_link has-tooltip", title: title, data: { container: 'body' } ).html_safe
+ link_to(author_html, user_path(author), class: "author_link has-tooltip", title: title, data: { container: 'body' }).html_safe
end
end
@@ -430,13 +430,22 @@ module ProjectsHelper
end
def visibility_select_options(project, selected_level)
- levels_options_array = Gitlab::VisibilityLevel.values.map do |level|
- [
+ level_options = Gitlab::VisibilityLevel.values.each_with_object([]) do |level, level_options|
+ next if restricted_levels.include?(level)
+
+ level_options << [
visibility_level_label(level),
{ data: { description: visibility_level_description(level, project) } },
level
]
end
- options_for_select(levels_options_array, selected_level)
+
+ options_for_select(level_options, selected_level)
+ end
+
+ def restricted_levels
+ return [] if current_user.admin?
+
+ current_application_settings.restricted_visibility_levels || []
end
end
diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb
index 8c02b4061ca..979264c9421 100644
--- a/app/helpers/snippets_helper.rb
+++ b/app/helpers/snippets_helper.rb
@@ -42,7 +42,7 @@ module SnippetsHelper
0,
lined_content.size,
surrounding_lines
- ) if line.include?(query)
+ ) if line.downcase.include?(query.downcase)
end
used_lines.uniq.sort
diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb
index 169cedeb796..b4aaf498068 100644
--- a/app/helpers/visibility_level_helper.rb
+++ b/app/helpers/visibility_level_helper.rb
@@ -85,7 +85,7 @@ module VisibilityLevelHelper
end
def restricted_visibility_levels(show_all = false)
- return [] if current_user.is_admin? && !show_all
+ return [] if current_user.admin? && !show_all
current_application_settings.restricted_visibility_levels || []
end
diff --git a/app/helpers/webpack_helper.rb b/app/helpers/webpack_helper.rb
new file mode 100644
index 00000000000..6bacda9fe75
--- /dev/null
+++ b/app/helpers/webpack_helper.rb
@@ -0,0 +1,30 @@
+require 'webpack/rails/manifest'
+
+module WebpackHelper
+ def webpack_bundle_tag(bundle)
+ javascript_include_tag(*gitlab_webpack_asset_paths(bundle))
+ end
+
+ # override webpack-rails gem helper until changes can make it upstream
+ def gitlab_webpack_asset_paths(source, extension: nil)
+ return "" unless source.present?
+
+ paths = Webpack::Rails::Manifest.asset_paths(source)
+ if extension
+ paths = paths.select { |p| p.ends_with? ".#{extension}" }
+ end
+
+ # include full webpack-dev-server url for rspec tests running locally
+ if Rails.env.test? && Rails.configuration.webpack.dev_server.enabled
+ host = Rails.configuration.webpack.dev_server.host
+ port = Rails.configuration.webpack.dev_server.port
+ protocol = Rails.configuration.webpack.dev_server.https ? 'https' : 'http'
+
+ paths.map! do |p|
+ "#{protocol}://#{host}:#{port}#{p}"
+ end
+ end
+
+ paths
+ end
+end
diff --git a/app/models/abuse_report.rb b/app/models/abuse_report.rb
index 2340453831e..0d7c2d20029 100644
--- a/app/models/abuse_report.rb
+++ b/app/models/abuse_report.rb
@@ -16,7 +16,7 @@ class AbuseReport < ActiveRecord::Base
def remove_user(deleted_by:)
user.block
- DeleteUserWorker.perform_async(deleted_by.id, user.id, delete_solo_owned_groups: true)
+ DeleteUserWorker.perform_async(deleted_by.id, user.id, delete_solo_owned_groups: true, hard_delete: true)
end
def notify
diff --git a/app/models/blob.rb b/app/models/blob.rb
index 801d3442803..55872acef51 100644
--- a/app/models/blob.rb
+++ b/app/models/blob.rb
@@ -42,12 +42,16 @@ class Blob < SimpleDelegator
size && truncated?
end
+ def extension
+ extname.downcase.delete('.')
+ end
+
def svg?
text? && language && language.name == 'SVG'
end
def pdf?
- name && File.extname(name) == '.pdf'
+ extension == 'pdf'
end
def ipython_notebook?
@@ -55,11 +59,15 @@ class Blob < SimpleDelegator
end
def sketch?
- binary? && extname.downcase.delete('.') == 'sketch'
+ binary? && extension == 'sketch'
end
def stl?
- extname.downcase.delete('.') == 'stl'
+ extension == 'stl'
+ end
+
+ def markup?
+ text? && Gitlab::MarkupHelper.markup?(name)
end
def size_within_svg_limits?
@@ -77,8 +85,10 @@ class Blob < SimpleDelegator
else
'text'
end
- elsif image? || svg?
+ elsif image?
'image'
+ elsif svg?
+ 'svg'
elsif pdf?
'pdf'
elsif ipython_notebook?
@@ -87,8 +97,18 @@ class Blob < SimpleDelegator
'sketch'
elsif stl?
'stl'
+ elsif markup?
+ if only_display_raw?
+ 'too_large'
+ else
+ 'markup'
+ end
elsif text?
- 'text'
+ if only_display_raw?
+ 'too_large'
+ else
+ 'text'
+ end
else
'download'
end
diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb
index b59e235c425..2f64f70685a 100644
--- a/app/models/ci/trigger.rb
+++ b/app/models/ci/trigger.rb
@@ -7,7 +7,7 @@ module Ci
belongs_to :project
belongs_to :owner, class_name: "User"
- has_many :trigger_requests, dependent: :destroy
+ has_many :trigger_requests
has_one :trigger_schedule, dependent: :destroy
validates :token, presence: true, uniqueness: true
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 5c452f78546..8b8b3f00202 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -326,14 +326,13 @@ class Commit
end
def raw_diffs(*args)
- use_gitaly = Gitlab::GitalyClient.feature_enabled?(:commit_raw_diffs)
- deltas_only = args.last.is_a?(Hash) && args.last[:deltas_only]
-
- if use_gitaly && !deltas_only
- Gitlab::GitalyClient::Commit.diff_from_parent(self, *args)
- else
- raw.diffs(*args)
- end
+ # NOTE: This feature is intentionally disabled until
+ # https://gitlab.com/gitlab-org/gitaly/issues/178 is resolved
+ # if Gitlab::GitalyClient.feature_enabled?(:commit_raw_diffs)
+ # Gitlab::GitalyClient::Commit.diff_from_parent(self, *args)
+ # else
+ raw.diffs(*args)
+ # end
end
def diffs(diff_options = nil)
diff --git a/app/models/concerns/discussion_on_diff.rb b/app/models/concerns/discussion_on_diff.rb
index 87db0c810c3..8ee42875670 100644
--- a/app/models/concerns/discussion_on_diff.rb
+++ b/app/models/concerns/discussion_on_diff.rb
@@ -2,11 +2,9 @@
module DiscussionOnDiff
extend ActiveSupport::Concern
- included do
- NUMBER_OF_TRUNCATED_DIFF_LINES = 16
-
- memoized_values << :active
+ NUMBER_OF_TRUNCATED_DIFF_LINES = 16
+ included do
delegate :line_code,
:original_line_code,
:diff_file,
@@ -29,12 +27,6 @@ module DiscussionOnDiff
true
end
- def active?
- return @active if @active.present?
-
- @active = first_note.active?
- end
-
# Returns an array of at most 16 highlighted lines above a diff note
def truncated_diff_lines(highlight: true)
lines = highlight ? highlighted_diff_lines : diff_lines
diff --git a/app/models/concerns/note_on_diff.rb b/app/models/concerns/note_on_diff.rb
index 1a5a7007a2b..6c27dd5aa5c 100644
--- a/app/models/concerns/note_on_diff.rb
+++ b/app/models/concerns/note_on_diff.rb
@@ -25,4 +25,18 @@ module NoteOnDiff
def diff_attributes
raise NotImplementedError
end
+
+ def active?(diff_refs = nil)
+ raise NotImplementedError
+ end
+
+ private
+
+ def noteable_diff_refs
+ if noteable.respond_to?(:diff_sha_refs)
+ noteable.diff_sha_refs
+ else
+ noteable.diff_refs
+ end
+ end
end
diff --git a/app/models/concerns/noteable.rb b/app/models/concerns/noteable.rb
index 772ff6a6d2f..dd1e6630642 100644
--- a/app/models/concerns/noteable.rb
+++ b/app/models/concerns/noteable.rb
@@ -36,10 +36,10 @@ module Noteable
.discussions(self)
end
- def grouped_diff_discussions
+ def grouped_diff_discussions(*args)
# Doesn't use `discussion_notes`, because this may include commit diff notes
# besides MR diff notes, that we do no want to display on the MR Changes tab.
- notes.inc_relations_for_view.grouped_diff_discussions
+ notes.inc_relations_for_view.grouped_diff_discussions(*args)
end
def resolvable_discussions
diff --git a/app/models/container_repository.rb b/app/models/container_repository.rb
index 9682df3a586..d0c94d3b694 100644
--- a/app/models/container_repository.rb
+++ b/app/models/container_repository.rb
@@ -20,7 +20,12 @@ class ContainerRepository < ActiveRecord::Base
end
def path
- @path ||= [project.full_path, name].select(&:present?).join('/')
+ @path ||= [project.full_path, name]
+ .select(&:present?).join('/').downcase
+ end
+
+ def location
+ File.join(registry.path, path)
end
def tag(tag)
diff --git a/app/models/diff_discussion.rb b/app/models/diff_discussion.rb
index d9b7e484e0f..6a6466b493b 100644
--- a/app/models/diff_discussion.rb
+++ b/app/models/diff_discussion.rb
@@ -10,6 +10,7 @@ class DiffDiscussion < Discussion
delegate :position,
:original_position,
+ :latest_merge_request_diff,
to: :first_note
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index 1523244f8a8..abe4518d62a 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -65,20 +65,18 @@ class DiffNote < Note
self.position.diff_refs == diff_refs
end
+ def latest_merge_request_diff
+ return unless for_merge_request?
+
+ self.noteable.merge_request_diff_for(self.position.diff_refs)
+ end
+
private
def supported?
for_commit? || self.noteable.has_complete_diff_refs?
end
- def noteable_diff_refs
- if noteable.respond_to?(:diff_sha_refs)
- noteable.diff_sha_refs
- else
- noteable.diff_refs
- end
- end
-
def set_original_position
self.original_position = self.position.dup unless self.original_position&.complete?
end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index d8d9db477d2..d39ae3a6c92 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -26,8 +26,6 @@ class Issue < ActiveRecord::Base
validates :project, presence: true
- scope :cared, ->(user) { where(assignee_id: user) }
- scope :open_for, ->(user) { opened.assigned_to(user) }
scope :in_projects, ->(project_ids) { where(project_id: project_ids) }
scope :without_due_date, -> { where(due_date: nil) }
diff --git a/app/models/label.rb b/app/models/label.rb
index 568fa6d44f5..d8b0e250732 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -21,6 +21,8 @@ class Label < ActiveRecord::Base
has_many :issues, through: :label_links, source: :target, source_type: 'Issue'
has_many :merge_requests, through: :label_links, source: :target, source_type: 'MergeRequest'
+ before_validation :strip_whitespace_from_title_and_color
+
validates :color, color: true, allow_blank: false
# Don't allow ',' for label titles
@@ -193,4 +195,8 @@ class Label < ActiveRecord::Base
def sanitize_title(value)
CGI.unescapeHTML(Sanitize.clean(value.to_s))
end
+
+ def strip_whitespace_from_title_and_color
+ %w(color title).each { |attr| self[attr] = self[attr]&.strip }
+ end
end
diff --git a/app/models/legacy_diff_discussion.rb b/app/models/legacy_diff_discussion.rb
index cb2651a03f8..e617ce36f56 100644
--- a/app/models/legacy_diff_discussion.rb
+++ b/app/models/legacy_diff_discussion.rb
@@ -7,6 +7,8 @@
class LegacyDiffDiscussion < Discussion
include DiscussionOnDiff
+ memoized_values << :active
+
def legacy_diff_discussion?
true
end
@@ -15,6 +17,12 @@ class LegacyDiffDiscussion < Discussion
LegacyDiffNote
end
+ def active?(*args)
+ return @active if @active.present?
+
+ @active = first_note.active?(*args)
+ end
+
def collapsed?
!active?
end
diff --git a/app/models/legacy_diff_note.rb b/app/models/legacy_diff_note.rb
index 9a77557ebcd..d7c627432d2 100644
--- a/app/models/legacy_diff_note.rb
+++ b/app/models/legacy_diff_note.rb
@@ -56,11 +56,12 @@ class LegacyDiffNote < Note
#
# If the note's current diff cannot be matched in the MergeRequest's current
# diff, it's considered inactive.
- def active?
+ def active?(diff_refs = nil)
return @active if defined?(@active)
return true if for_commit?
return true unless diff_line
return false unless noteable
+ return false if diff_refs && diff_refs != noteable_diff_refs
noteable_diff = find_noteable_diff
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index b71a9e17a93..1d4827375d7 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -104,7 +104,6 @@ class MergeRequest < ActiveRecord::Base
scope :by_source_or_target_branch, ->(branch_name) do
where("source_branch = :branch OR target_branch = :branch", branch: branch_name)
end
- scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
scope :by_milestone, ->(milestone) { where(milestone_id: milestone) }
scope :of_projects, ->(ids) { where(target_project_id: ids) }
scope :from_project, ->(project) { where(source_project_id: project.id) }
@@ -367,6 +366,14 @@ class MergeRequest < ActiveRecord::Base
merge_request_diff(true)
end
+ def merge_request_diff_for(diff_refs)
+ @merge_request_diffs_by_diff_refs ||= Hash.new do |h, diff_refs|
+ h[diff_refs] = merge_request_diffs.viewable.select_without_diff.find_by_diff_refs(diff_refs)
+ end
+
+ @merge_request_diffs_by_diff_refs[diff_refs]
+ end
+
def reload_diff_if_branch_changed
if source_branch_changed? || target_branch_changed?
reload_diff
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 6ad56b842b2..6604af2b47e 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -31,6 +31,10 @@ class MergeRequestDiff < ActiveRecord::Base
# It allows you to override variables like head_commit_sha before getting diff.
after_create :save_git_content, unless: :importing?
+ def self.find_by_diff_refs(diff_refs)
+ find_by(start_commit_sha: diff_refs.start_sha, head_commit_sha: diff_refs.head_sha, base_commit_sha: diff_refs.base_sha)
+ end
+
def self.select_without_diff
select(column_names - ['st_diffs'])
end
@@ -130,6 +134,12 @@ class MergeRequestDiff < ActiveRecord::Base
st_commits.map { |commit| commit[:id] }
end
+ def diff_refs=(new_diff_refs)
+ self.base_commit_sha = new_diff_refs&.base_sha
+ self.start_commit_sha = new_diff_refs&.start_sha
+ self.head_commit_sha = new_diff_refs&.head_sha
+ end
+
def diff_refs
return unless start_commit_sha || base_commit_sha
diff --git a/app/models/note.rb b/app/models/note.rb
index 1ea7b946061..630d0adbece 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -96,6 +96,7 @@ class Note < ActiveRecord::Base
before_validation :set_discussion_id, on: :create
after_save :keep_around_commit, unless: :for_personal_snippet?
after_save :expire_etag_cache
+ after_destroy :expire_etag_cache
class << self
def model_name
@@ -113,11 +114,11 @@ class Note < ActiveRecord::Base
Discussion.build(notes)
end
- def grouped_diff_discussions
+ def grouped_diff_discussions(diff_refs = nil)
diff_notes.
fresh.
discussions.
- select(&:active?).
+ select { |n| n.active?(diff_refs) }.
group_by(&:line_code)
end
@@ -140,6 +141,10 @@ class Note < ActiveRecord::Base
true
end
+ def latest_merge_request_diff
+ nil
+ end
+
def max_attachment_size
current_application_settings.max_attachment_size.megabytes.to_i
end
diff --git a/app/models/project_services/chat_notification_service.rb b/app/models/project_services/chat_notification_service.rb
index fa782c6fbb7..f2dfb87dbda 100644
--- a/app/models/project_services/chat_notification_service.rb
+++ b/app/models/project_services/chat_notification_service.rb
@@ -22,7 +22,7 @@ class ChatNotificationService < Service
end
def can_test?
- valid?
+ super && valid?
end
def self.supported_events
diff --git a/app/models/repository.rb b/app/models/repository.rb
index f4c51cdfdf4..7bb874d7744 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -19,7 +19,7 @@ class Repository
#
# For example, for entry `:readme` there's a method called `readme` which
# stores its data in the `readme` cache key.
- CACHED_METHODS = %i(size commit_count readme version contribution_guide
+ CACHED_METHODS = %i(size commit_count readme 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).freeze
@@ -32,7 +32,6 @@ class Repository
changelog: :changelog,
license: %i(license_blob license_key),
contributing: :contribution_guide,
- version: :version,
gitignore: :gitignore,
koding: :koding_yml,
gitlab_ci: :gitlab_ci_yml,
@@ -109,7 +108,7 @@ class Repository
offset: offset,
after: after,
before: before,
- follow: path.present?,
+ follow: Array(path).length == 1,
skip_merges: skip_merges
}
@@ -407,8 +406,6 @@ class Repository
# Runs code after a repository has been forked/imported.
def after_import
expire_content_cache
- expire_tags_cache
- expire_branches_cache
end
# Runs code after a new commit has been pushed.
@@ -532,11 +529,6 @@ class Repository
end
cache_method :readme
- def version
- file_on_head(:version)
- end
- cache_method :version
-
def contribution_guide
file_on_head(:contributing)
end
@@ -965,13 +957,15 @@ class Repository
end
def is_ancestor?(ancestor_id, descendant_id)
- Gitlab::GitalyClient.migrate(:is_ancestor) do |is_enabled|
- if is_enabled
- raw_repository.is_ancestor?(ancestor_id, descendant_id)
- else
- merge_base_commit(ancestor_id, descendant_id) == ancestor_id
- end
- end
+ # NOTE: This feature is intentionally disabled until
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/30586 is resolved
+ # Gitlab::GitalyClient.migrate(:is_ancestor) do |is_enabled|
+ # if is_enabled
+ # raw_repository.is_ancestor?(ancestor_id, descendant_id)
+ # else
+ merge_base_commit(ancestor_id, descendant_id) == ancestor_id
+ # end
+ # end
end
def empty_repo?
diff --git a/app/models/spam_log.rb b/app/models/spam_log.rb
index 3b8b9833565..dd21ee15c6c 100644
--- a/app/models/spam_log.rb
+++ b/app/models/spam_log.rb
@@ -3,9 +3,9 @@ class SpamLog < ActiveRecord::Base
validates :user, presence: true
- def remove_user
+ def remove_user(deleted_by:)
user.block
- user.destroy
+ DeleteUserWorker.perform_async(deleted_by.id, user.id, delete_solo_owned_groups: true, hard_delete: true)
end
def text
diff --git a/app/models/user.rb b/app/models/user.rb
index 87eeee204f8..457ba05fb04 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -555,10 +555,6 @@ class User < ActiveRecord::Base
authorized_projects(Gitlab::Access::REPORTER).non_archived.with_issues_enabled
end
- def is_admin?
- admin
- end
-
def require_ssh_key?
keys.count == 0 && Gitlab::ProtocolAccess.allowed?('ssh')
end
@@ -591,10 +587,6 @@ class User < ActiveRecord::Base
name.split.first unless name.blank?
end
- def cared_merge_requests
- MergeRequest.cared(self)
- end
-
def projects_limit_left
projects_limit - personal_projects.count
end
diff --git a/app/policies/ci/runner_policy.rb b/app/policies/ci/runner_policy.rb
index 7edd383530d..416d93ffe63 100644
--- a/app/policies/ci/runner_policy.rb
+++ b/app/policies/ci/runner_policy.rb
@@ -3,7 +3,7 @@ module Ci
def rules
return unless @user
- can! :assign_runner if @user.is_admin?
+ can! :assign_runner if @user.admin?
return if @subject.is_shared? || @subject.locked?
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index cb58c115d54..87398303c68 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -28,6 +28,7 @@ class GroupPolicy < BasePolicy
can! :admin_namespace
can! :admin_group_member
can! :change_visibility_level
+ can! :create_subgroup if @user.can_create_group
end
if globally_viewable && @subject.request_access_enabled && !member
diff --git a/app/serializers/status_entity.rb b/app/serializers/status_entity.rb
index dfd9d1584a1..944472f3e51 100644
--- a/app/serializers/status_entity.rb
+++ b/app/serializers/status_entity.rb
@@ -1,8 +1,12 @@
class StatusEntity < Grape::Entity
include RequestAwareEntity
- expose :icon, :favicon, :text, :label, :group
+ expose :icon, :text, :label, :group
expose :has_details?, as: :has_details
expose :details_path
+
+ expose :favicon do |status|
+ ActionController::Base.helpers.image_path(File.join('ci_favicons', "#{status.favicon}.ico"))
+ end
end
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index fbdaa455651..7828c5806b0 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -58,6 +58,9 @@ module Projects
fail(error: @project.errors.full_messages.join(', '))
end
@project
+ rescue ActiveRecord::RecordInvalid => e
+ message = "Unable to save #{e.record.type}: #{e.record.errors.full_messages.join(", ")} "
+ fail(error: message)
rescue => e
fail(error: e.message)
end
diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb
index 4c72d5e117d..eea17e24903 100644
--- a/app/services/projects/import_service.rb
+++ b/app/services/projects/import_service.rb
@@ -59,7 +59,6 @@ module Projects
project.repository.add_remote(project.import_type, project.import_url)
project.repository.set_remote_as_mirror(project.import_type)
project.repository.fetch_remote(project.import_type, forced: true)
- project.repository.remove_remote(project.import_type)
end
def import_data
diff --git a/app/services/search/global_service.rb b/app/services/search/global_service.rb
index 8409b592b72..ff188102b62 100644
--- a/app/services/search/global_service.rb
+++ b/app/services/search/global_service.rb
@@ -7,16 +7,13 @@ module Search
end
def execute
- group = Group.find_by(id: params[:group_id]) if params[:group_id].present?
- projects = ProjectsFinder.new(current_user: current_user).execute
-
- if group
- projects = projects.inside_path(group.full_path)
- end
-
Gitlab::SearchResults.new(current_user, projects, params[:search])
end
+ def projects
+ @projects ||= ProjectsFinder.new(current_user: current_user).execute
+ end
+
def scope
@scope ||= begin
allowed_scopes = %w[issues merge_requests milestones]
diff --git a/app/services/search/group_service.rb b/app/services/search/group_service.rb
new file mode 100644
index 00000000000..29478e3251f
--- /dev/null
+++ b/app/services/search/group_service.rb
@@ -0,0 +1,18 @@
+module Search
+ class GroupService < Search::GlobalService
+ attr_accessor :group
+
+ def initialize(user, group, params)
+ super(user, params)
+
+ @group = group
+ end
+
+ def projects
+ return Project.none unless group
+ return @projects if defined? @projects
+
+ @projects = super.inside_path(group.full_path)
+ end
+ end
+end
diff --git a/app/services/search_service.rb b/app/services/search_service.rb
index 8d46a8dab3e..22736c71725 100644
--- a/app/services/search_service.rb
+++ b/app/services/search_service.rb
@@ -54,6 +54,8 @@ class SearchService
Search::ProjectService.new(project, current_user, params)
elsif show_snippets?
Search::SnippetService.new(current_user, params)
+ elsif group
+ Search::GroupService.new(current_user, group, params)
else
Search::GlobalService.new(current_user, params)
end
diff --git a/app/services/users/build_service.rb b/app/services/users/build_service.rb
new file mode 100644
index 00000000000..9a0a5a12f91
--- /dev/null
+++ b/app/services/users/build_service.rb
@@ -0,0 +1,100 @@
+module Users
+ # Service for building a new user.
+ class BuildService < BaseService
+ def initialize(current_user, params = {})
+ @current_user = current_user
+ @params = params.dup
+ end
+
+ def execute
+ raise Gitlab::Access::AccessDeniedError unless can_create_user?
+
+ user = User.new(build_user_params)
+
+ if current_user&.admin?
+ if params[:reset_password]
+ user.generate_reset_token
+ params[:force_random_password] = true
+ end
+
+ if params[:force_random_password]
+ random_password = Devise.friendly_token.first(Devise.password_length.min)
+ user.password = user.password_confirmation = random_password
+ end
+ end
+
+ identity_attrs = params.slice(:extern_uid, :provider)
+
+ if identity_attrs.any?
+ user.identities.build(identity_attrs)
+ end
+
+ user
+ end
+
+ private
+
+ def can_create_user?
+ (current_user.nil? && current_application_settings.signup_enabled?) || current_user&.admin?
+ end
+
+ # Allowed params for creating a user (admins only)
+ def admin_create_params
+ [
+ :access_level,
+ :admin,
+ :avatar,
+ :bio,
+ :can_create_group,
+ :color_scheme_id,
+ :email,
+ :external,
+ :force_random_password,
+ :hide_no_password,
+ :hide_no_ssh_key,
+ :key_id,
+ :linkedin,
+ :name,
+ :password,
+ :password_automatically_set,
+ :password_expires_at,
+ :projects_limit,
+ :remember_me,
+ :skip_confirmation,
+ :skype,
+ :theme_id,
+ :twitter,
+ :username,
+ :website_url
+ ]
+ end
+
+ # Allowed params for user signup
+ def signup_params
+ [
+ :email,
+ :email_confirmation,
+ :password_automatically_set,
+ :name,
+ :password,
+ :username
+ ]
+ end
+
+ def build_user_params
+ if current_user&.admin?
+ user_params = params.slice(*admin_create_params)
+ user_params[:created_by_id] = current_user&.id
+
+ if params[:reset_password]
+ user_params.merge!(force_random_password: true, password_expires_at: nil)
+ end
+ else
+ user_params = params.slice(*signup_params)
+ user_params[:skip_confirmation] = !current_application_settings.send_user_confirmation_email
+ end
+
+ user_params
+ end
+ end
+end
diff --git a/app/services/users/create_service.rb b/app/services/users/create_service.rb
index a847a71a66a..a2105d31f71 100644
--- a/app/services/users/create_service.rb
+++ b/app/services/users/create_service.rb
@@ -6,34 +6,10 @@ module Users
@params = params.dup
end
- def build
- raise Gitlab::Access::AccessDeniedError unless can_create_user?
-
- user = User.new(build_user_params)
-
- if current_user&.is_admin?
- if params[:reset_password]
- @reset_token = user.generate_reset_token
- params[:force_random_password] = true
- end
-
- if params[:force_random_password]
- random_password = Devise.friendly_token.first(Devise.password_length.min)
- user.password = user.password_confirmation = random_password
- end
- end
-
- identity_attrs = params.slice(:extern_uid, :provider)
-
- if identity_attrs.any?
- user.identities.build(identity_attrs)
- end
-
- user
- end
-
def execute
- user = build
+ user = Users::BuildService.new(current_user, params).execute
+
+ @reset_token = user.generate_reset_token if user.recently_sent_password_reset?
if user.save
log_info("User \"#{user.name}\" (#{user.email}) was created")
@@ -43,70 +19,5 @@ module Users
user
end
-
- private
-
- def can_create_user?
- (current_user.nil? && current_application_settings.signup_enabled?) || current_user&.is_admin?
- end
-
- # Allowed params for creating a user (admins only)
- def admin_create_params
- [
- :access_level,
- :admin,
- :avatar,
- :bio,
- :can_create_group,
- :color_scheme_id,
- :email,
- :external,
- :force_random_password,
- :password_automatically_set,
- :hide_no_password,
- :hide_no_ssh_key,
- :key_id,
- :linkedin,
- :name,
- :password,
- :password_expires_at,
- :projects_limit,
- :remember_me,
- :skip_confirmation,
- :skype,
- :theme_id,
- :twitter,
- :username,
- :website_url
- ]
- end
-
- # Allowed params for user signup
- def signup_params
- [
- :email,
- :email_confirmation,
- :password_automatically_set,
- :name,
- :password,
- :username
- ]
- end
-
- def build_user_params
- if current_user&.is_admin?
- user_params = params.slice(*admin_create_params)
- user_params[:created_by_id] = current_user&.id
-
- if params[:reset_password]
- user_params.merge!(force_random_password: true, password_expires_at: nil)
- end
- else
- user_params = params.slice(*signup_params)
- user_params[:skip_confirmation] = !current_application_settings.send_user_confirmation_email
- end
-
- user_params
- end
end
end
diff --git a/app/services/users/destroy_service.rb b/app/services/users/destroy_service.rb
index ba58b174cc0..9eb6a600f6b 100644
--- a/app/services/users/destroy_service.rb
+++ b/app/services/users/destroy_service.rb
@@ -26,7 +26,7 @@ module Users
::Projects::DestroyService.new(project, current_user, skip_repo: true).execute
end
- MigrateToGhostUserService.new(user).execute
+ MigrateToGhostUserService.new(user).execute unless options[:hard_delete]
# Destroy the namespace after destroying the user since certain methods may depend on the namespace existing
namespace = user.namespace
diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb
index d6ccf0dc92c..d2783ce5b2f 100644
--- a/app/uploaders/file_uploader.rb
+++ b/app/uploaders/file_uploader.rb
@@ -38,10 +38,6 @@ class FileUploader < GitlabUploader
File.join(dynamic_path_segment, @secret)
end
- def cache_dir
- File.join(base_dir, 'tmp', @project.path_with_namespace, @secret)
- end
-
def model
project
end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 703f611bb45..f4ba44096d3 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -571,6 +571,7 @@
The multiplier can also have a decimal value.
The default value (1) is a reasonable choice for the majority of GitLab
installations. Set to 0 to completely disable polling.
+ = link_to icon('question-circle'), help_page_path('administration/polling')
.form-actions
= f.submit 'Save', class: 'btn btn-save'
diff --git a/app/views/admin/health_check/show.html.haml b/app/views/admin/health_check/show.html.haml
index e79303240f0..6a208d76a38 100644
--- a/app/views/admin/health_check/show.html.haml
+++ b/app/views/admin/health_check/show.html.haml
@@ -13,7 +13,7 @@
= button_to reset_health_check_token_admin_application_settings_path,
method: :put, class: 'btn btn-default',
data: { confirm: 'Are you sure you want to reset the health check token?' } do
- = icon('refresh')
+ = icon('spinner')
Reset health check access token
%p.light
Health information can be retrieved as plain text, JSON, or XML using:
diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml
index 7d26864d0f3..f118804cace 100644
--- a/app/views/admin/runners/index.html.haml
+++ b/app/views/admin/runners/index.html.haml
@@ -21,7 +21,7 @@
= button_to reset_runners_token_admin_application_settings_path,
method: :put, class: 'btn btn-default',
data: { confirm: 'Are you sure you want to reset registration token?' } do
- = icon('refresh')
+ = icon('spinner')
Reset runners registration token
.bs-callout
diff --git a/app/views/admin/services/index.html.haml b/app/views/admin/services/index.html.haml
index 6a5986f496a..50132572096 100644
--- a/app/views/admin/services/index.html.haml
+++ b/app/views/admin/services/index.html.haml
@@ -13,7 +13,7 @@
- @services.sort_by(&:title).each do |service|
%tr
%td
- = icon("copy", class: 'clgray')
+ = boolean_to_icon service.activated?
%td
= link_to edit_admin_application_settings_service_path(service.id) do
%strong= service.title
diff --git a/app/views/award_emoji/_awards_block.html.haml b/app/views/award_emoji/_awards_block.html.haml
index 3ca45fbf751..9aabfb49a29 100644
--- a/app/views/award_emoji/_awards_block.html.haml
+++ b/app/views/award_emoji/_awards_block.html.haml
@@ -1,8 +1,9 @@
- grouped_emojis = awardable.grouped_awards(with_thumbs: inline)
+- user_authored = awardable.user_authored?(current_user)
.awards.js-awards-block{ class: ("hidden" if !inline && grouped_emojis.empty?), data: { award_url: toggle_award_url(awardable) } }
- awards_sort(grouped_emojis).each do |emoji, awards|
%button.btn.award-control.js-emoji-btn.has-tooltip{ type: "button",
- class: (award_state_class(awards, current_user)),
+ class: [(award_state_class(awards, current_user)), (award_user_authored_class(emoji) if user_authored)],
data: { placement: "bottom", title: award_user_list(awards, current_user) } }
= emoji_icon(emoji)
%span.award-control-text.js-counter
@@ -12,6 +13,7 @@
.award-menu-holder.js-award-holder
%button.btn.award-control.has-tooltip.js-add-award{ type: 'button',
'aria-label': 'Add emoji',
+ class: ("js-user-authored" if user_authored),
data: { title: 'Add emoji', placement: "bottom" } }
%span{ class: "award-control-icon award-control-icon-neutral" }= custom_icon('emoji_slightly_smiling_face')
%span{ class: "award-control-icon award-control-icon-positive" }= custom_icon('emoji_smiley')
diff --git a/app/views/discussions/_discussion.html.haml b/app/views/discussions/_discussion.html.haml
index e04958817e4..8440fb3d785 100644
--- a/app/views/discussions/_discussion.html.haml
+++ b/app/views/discussions/_discussion.html.haml
@@ -20,21 +20,22 @@
= discussion.author.to_reference
started a discussion
+ - url = discussion_diff_path(discussion)
- if discussion.for_commit? && @noteable != discussion.noteable
on
- commit = discussion.noteable
- if commit
commit
- - anchor = discussion.line_code if discussion.diff_discussion?
- = link_to commit.short_id, namespace_project_commit_path(discussion.project.namespace, discussion.project, discussion.noteable, anchor: anchor), class: 'monospace'
+ = link_to commit.short_id, url, class: 'monospace'
- else
a deleted commit
- elsif discussion.diff_discussion?
on
- - if discussion.active?
- = link_to 'the diff', discussion_diff_path(discussion)
- - else
- an outdated diff
+ = conditional_link_to url.present?, url do
+ - if discussion.active?
+ the diff
+ - else
+ an outdated diff
= time_ago_with_tooltip(discussion.created_at, placement: "bottom", html_class: "note-created-ago")
= render "discussions/headline", discussion: discussion
diff --git a/app/views/events/event/_common.html.haml b/app/views/events/event/_common.html.haml
index af97e9588a5..01e72862114 100644
--- a/app/views/events/event/_common.html.haml
+++ b/app/views/events/event/_common.html.haml
@@ -1,13 +1,4 @@
-- if event.target
- - if event.action_name == "opened"
- .profile-icon.open-icon
- = custom_icon("icon_status_open")
- - elsif event.action_name == "closed"
- .profile-icon.closed-icon
- = custom_icon("icon_status_closed")
- - else
- .profile-icon.fork-icon
- = custom_icon("icon_code_fork")
+= icon_for_profile_event(event)
.event-title
%span.author_name= link_to_author event
diff --git a/app/views/events/event/_created_project.html.haml b/app/views/events/event/_created_project.html.haml
index fee85c94277..d8e59be57bb 100644
--- a/app/views/events/event/_created_project.html.haml
+++ b/app/views/events/event/_created_project.html.haml
@@ -1,5 +1,4 @@
-.profile-icon.open-icon
- = custom_icon("icon_status_open")
+= icon_for_profile_event(event)
.event-title
%span.author_name= link_to_author event
diff --git a/app/views/events/event/_note.html.haml b/app/views/events/event/_note.html.haml
index 83709f5e4d0..df4b9562215 100644
--- a/app/views/events/event/_note.html.haml
+++ b/app/views/events/event/_note.html.haml
@@ -1,5 +1,4 @@
-.profile-icon
- = custom_icon("icon_comment_o")
+= icon_for_profile_event(event)
.event-title
%span.author_name= link_to_author event
diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml
index efdc8764acf..c0943100ae3 100644
--- a/app/views/events/event/_push.html.haml
+++ b/app/views/events/event/_push.html.haml
@@ -1,10 +1,6 @@
- project = event.project
-.profile-icon
- - if event.action_name == "deleted"
- = custom_icon("trash_o")
- - else
- = custom_icon("icon_commit")
+= icon_for_profile_event(event)
.event-title
%span.author_name= link_to_author event
diff --git a/app/views/groups/subgroups.html.haml b/app/views/groups/subgroups.html.haml
index be809083139..8f0724c0677 100644
--- a/app/views/groups/subgroups.html.haml
+++ b/app/views/groups/subgroups.html.haml
@@ -9,7 +9,7 @@
.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
+ - if can?(current_user, :create_subgroup, @group)
= link_to new_group_path(parent_id: @group.id), class: 'btn btn-new pull-right' do
New Subgroup
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index 700c5e61a14..ea8bbe92d86 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -318,3 +318,11 @@
%td.shortcut
.key l
%td Change Label
+ %tbody.hidden-shortcut.wiki{ style: 'display:none' }
+ %tr
+ %th
+ %th Wiki pages
+ %tr
+ %td.shortcut
+ .key e
+ %td Edit wiki page
diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml
index 207f80bedfe..615dd56afbd 100644
--- a/app/views/help/ui.html.haml
+++ b/app/views/help/ui.html.haml
@@ -252,7 +252,7 @@
= icon('chevron-down')
.dropdown-menu.dropdown-select.dropdown-menu-selectable
.dropdown-title
- %span Dropdown Title
+ %span Dropdown title
%button.dropdown-title-button.dropdown-menu-close{ aria: { label: "Close" } }
= icon('times')
.dropdown-input
@@ -291,7 +291,7 @@
= icon('chevron-down')
.dropdown-menu.dropdown-select.dropdown-menu-selectable.is-loading
.dropdown-title
- %span Dropdown Title
+ %span Dropdown title
%button.dropdown-title-button.dropdown-menu-close{ aria: { label: "Close" } }
= icon('times')
.dropdown-input
@@ -335,7 +335,7 @@
= icon('chevron-down')
.dropdown-menu.dropdown-select.dropdown-menu-selectable.dropdown-menu-user
.dropdown-title
- %span Dropdown Title
+ %span Dropdown title
%button.dropdown-title-button.dropdown-menu-close{ aria: { label: "Close" } }
= icon('times')
.dropdown-input
@@ -362,7 +362,7 @@
.dropdown-title
%button.dropdown-title-button.dropdown-menu-back{ aria: { label: "Go back" } }
= icon('arrow-left')
- %span Dropdown Title
+ %span Dropdown title
%button.dropdown-title-button.dropdown-menu-close{ aria: { label: "Close" } }
= icon('times')
.dropdown-input
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index a611481a0a4..19473b6ab27 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -28,9 +28,9 @@
= stylesheet_link_tag "application", media: "all"
= stylesheet_link_tag "print", media: "print"
- = javascript_include_tag(*webpack_asset_paths("runtime"))
- = javascript_include_tag(*webpack_asset_paths("common"))
- = javascript_include_tag(*webpack_asset_paths("main"))
+ = webpack_bundle_tag "runtime"
+ = webpack_bundle_tag "common"
+ = webpack_bundle_tag "main"
- if content_for?(:page_specific_javascripts)
= yield :page_specific_javascripts
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 17e553aeef0..a9893dea68f 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -31,7 +31,7 @@
%li.impersonation
= link_to admin_impersonation_path, method: :delete, title: "Stop impersonation", aria: { label: 'Stop impersonation' }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= icon('user-secret fw')
- - if current_user.is_admin?
+ - if current_user.admin?
%li
= link_to admin_root_path, title: 'Admin area', aria: { label: "Admin area" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('wrench fw')
diff --git a/app/views/layouts/nav/_explore.html.haml b/app/views/layouts/nav/_explore.html.haml
index 3a1fcd00e9c..0cb367452f7 100644
--- a/app/views/layouts/nav/_explore.html.haml
+++ b/app/views/layouts/nav/_explore.html.haml
@@ -1,16 +1,29 @@
%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
+ = link_to explore_root_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
+ .shortcut-mappings
+ .key
+ = icon('arrow-up', 'aria-label' => 'hidden')
+ P
%span
Projects
= nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
- = link_to explore_groups_path, title: 'Groups' do
+ = link_to explore_groups_path, title: 'Groups', class: 'dashboard-shortcuts-groups' do
+ .shortcut-mappings
+ .key
+ = icon('arrow-up', 'aria-label' => 'hidden')
+ G
%span
Groups
= nav_link(controller: :snippets) do
- = link_to explore_snippets_path, title: 'Snippets' do
+ = link_to explore_snippets_path, title: 'Snippets', class: 'dashboard-shortcuts-snippets' do
+ .shortcut-mappings
+ .key
+ = icon('arrow-up', 'aria-label' => 'hidden')
+ S
%span
Snippets
+ %li.divider
= nav_link(controller: :help) do
= link_to help_path, title: 'Help' do
%span
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 299dace3406..e34cddeb3e2 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -23,7 +23,7 @@
Registry
- if project_nav_tab? :issues
- = nav_link(controller: [:issues, :labels, :milestones, :boards]) do
+ = nav_link(controller: @project.default_issues_tracker? ? [:issues, :labels, :milestones, :boards] : :issues) do
= link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues', class: 'shortcuts-issues' do
%span
Issues
@@ -31,7 +31,7 @@
%span.badge.count.issue_counter= number_with_delimiter(IssuesFinder.new(current_user, project_id: @project.id).execute.opened.count)
- if project_nav_tab? :merge_requests
- = nav_link(controller: :merge_requests) do
+ = nav_link(controller: @project.default_issues_tracker? ? :merge_requests : [:merge_requests, :labels, :milestones]) do
= link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
%span
Merge Requests
diff --git a/app/views/notify/project_was_exported_email.html.haml b/app/views/notify/project_was_exported_email.html.haml
index 76440926a2b..3def26342a1 100644
--- a/app/views/notify/project_was_exported_email.html.haml
+++ b/app/views/notify/project_was_exported_email.html.haml
@@ -2,7 +2,7 @@
Project #{@project.name} was exported successfully.
%p
The project export can be downloaded from:
- = link_to download_export_namespace_project_url(@project.namespace, @project), rel: 'nofollow', download: '', do
+ = link_to download_export_namespace_project_url(@project.namespace, @project), rel: 'nofollow', download: '' do
= @project.name_with_namespace + " export"
%p
The download link will expire in 24 hours.
diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index aa9b852035e..9aafff343f0 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -25,11 +25,10 @@
#blob-content-holder.blob-content-holder
%article.file-holder
= render "projects/blob/header", blob: blob
- - if current_user
- .js-file-fork-suggestion-section.file-fork-suggestion.hidden
- %span.file-fork-suggestion-note
- You don't have permission to edit this file. Try forking this project to edit the file.
- = link_to 'Fork', fork_path, method: :post, class: 'btn btn-grouped btn-inverted btn-new'
- %button.js-cancel-fork-suggestion.btn.btn-grouped{ type: 'button' }
- Cancel
- = render blob.to_partial_path(@project), blob: blob
+
+ - if blob.empty?
+ .file-content.code
+ .nothing-here-block
+ Empty file
+ - else
+ = render blob.to_partial_path(@project), blob: blob
diff --git a/app/views/projects/blob/_header.html.haml b/app/views/projects/blob/_header.html.haml
index 6c7d389e707..7a4a293548c 100644
--- a/app/views/projects/blob/_header.html.haml
+++ b/app/views/projects/blob/_header.html.haml
@@ -13,7 +13,7 @@
.file-actions.hidden-xs
.btn-group{ role: "group" }<
- = copy_blob_content_button(blob) if !blame && blob_text_viewable?(blob)
+ = copy_blob_content_button(blob) if !blame && blob_rendered_as_text?(blob)
= open_raw_file_button(namespace_project_raw_path(@project.namespace, @project, @id))
= view_on_environment_button(@commit.sha, @path, @environment) if @environment
@@ -38,3 +38,10 @@
- if current_user
= replace_blob_link
= delete_blob_link
+- if current_user
+ .js-file-fork-suggestion-section.file-fork-suggestion.hidden
+ %span.file-fork-suggestion-note
+ You don't have permission to edit this file. Try forking this project to edit the file.
+ = link_to 'Fork', fork_path, method: :post, class: 'btn btn-grouped btn-inverted btn-new'
+ %button.js-cancel-fork-suggestion.btn.btn-grouped{ type: 'button' }
+ Cancel
diff --git a/app/views/projects/blob/_image.html.haml b/app/views/projects/blob/_image.html.haml
index ea3cecb86a9..73877d730f5 100644
--- a/app/views/projects/blob/_image.html.haml
+++ b/app/views/projects/blob/_image.html.haml
@@ -1,15 +1,2 @@
.file-content.image_file
- - if blob.svg?
- - if blob.size_within_svg_limits?
- -# We need to scrub SVG but we cannot do so in the RawController: it would
- -# be wrong/strange if RawController modified the data.
- - blob.load_all_data!(@repository)
- - blob = sanitize_svg(blob)
- %img{ src: "data:#{blob.mime_type};base64,#{Base64.encode64(blob.data)}", alt: "#{blob.name}" }
- - else
- .nothing-here-block
- The SVG could not be displayed as it is too large, you can
- #{link_to('view the raw file', namespace_project_raw_path(@project.namespace, @project, @id), target: '_blank', rel: 'noopener noreferrer')}
- instead.
- - else
- %img{ src: namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.id, blob.path)), alt: "#{blob.name}" }
+ %img{ src: namespace_project_raw_path(@project.namespace, @project, @id), alt: blob.name }
diff --git a/app/views/projects/blob/_markup.html.haml b/app/views/projects/blob/_markup.html.haml
new file mode 100644
index 00000000000..4ee4b03ff04
--- /dev/null
+++ b/app/views/projects/blob/_markup.html.haml
@@ -0,0 +1,4 @@
+- blob.load_all_data!(@repository)
+
+.file-content.wiki
+ = render_markup(blob.name, blob.data)
diff --git a/app/views/projects/blob/_svg.html.haml b/app/views/projects/blob/_svg.html.haml
new file mode 100644
index 00000000000..93be58fc658
--- /dev/null
+++ b/app/views/projects/blob/_svg.html.haml
@@ -0,0 +1,9 @@
+- if blob.size_within_svg_limits?
+ -# We need to scrub SVG but we cannot do so in the RawController: it would
+ -# be wrong/strange if RawController modified the data.
+ - blob.load_all_data!(@repository)
+ - blob = sanitize_svg(blob)
+ .file-content.image_file
+ %img{ src: "data:#{blob.mime_type};base64,#{Base64.encode64(blob.data)}", alt: blob.name }
+- else
+ = render 'too_large'
diff --git a/app/views/projects/blob/_text.html.haml b/app/views/projects/blob/_text.html.haml
index 7b16d266982..20638f6961d 100644
--- a/app/views/projects/blob/_text.html.haml
+++ b/app/views/projects/blob/_text.html.haml
@@ -1,19 +1,2 @@
-- if blob.only_display_raw?
- .file-content.code
- .nothing-here-block
- File too large, you can
- = succeed '.' do
- = link_to 'view the raw file', namespace_project_raw_path(@project.namespace, @project, @id), target: '_blank', rel: 'noopener noreferrer'
-
-- else
- - blob.load_all_data!(@repository)
-
- - if blob.empty?
- .file-content.code
- .nothing-here-block Empty file
- - else
- - if markup?(blob.name)
- .file-content.wiki
- = render_markup(blob.name, blob.data)
- - else
- = render 'shared/file_highlight', blob: blob, repository: @repository
+- blob.load_all_data!(@repository)
+= render 'shared/file_highlight', blob: blob, repository: @repository
diff --git a/app/views/projects/blob/_too_large.html.haml b/app/views/projects/blob/_too_large.html.haml
new file mode 100644
index 00000000000..a505f87df40
--- /dev/null
+++ b/app/views/projects/blob/_too_large.html.haml
@@ -0,0 +1,5 @@
+.file-content.code
+ .nothing-here-block
+ The file could not be displayed as it is too large, you can
+ #{link_to('view the raw file', namespace_project_raw_path(@project.namespace, @project, @id), target: '_blank', rel: 'noopener noreferrer')}
+ instead.
diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml
index b6738c3380f..b9b3f3ec7a3 100644
--- a/app/views/projects/blob/show.html.haml
+++ b/app/views/projects/blob/show.html.haml
@@ -8,7 +8,7 @@
#tree-holder.tree-holder
= render 'blob', blob: @blob
- - if can_edit_blob?(@blob)
+ - if can_modify_blob?(@blob)
= render 'projects/blob/remove'
- title = "Replace #{@blob.name}"
diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml
index f4a66398c85..c4159ce1a36 100644
--- a/app/views/projects/builds/_sidebar.html.haml
+++ b/app/views/projects/builds/_sidebar.html.haml
@@ -136,7 +136,7 @@
- else
= build.id
- if build.retried?
- %i.fa.fa-refresh.has-tooltip{ data: { container: 'body', placement: 'bottom' }, title: 'Job was retried' }
+ %i.fa.fa-spinner.has-tooltip{ data: { container: 'body', placement: 'bottom' }, title: 'Job was retried' }
:javascript
new Sidebar();
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
index 0faad57a312..7cb2ec83cc7 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/builds/show.html.haml
@@ -71,11 +71,11 @@
= custom_icon('scroll_down_hover_active')
#up-build-trace
%pre.build-trace#build-trace
- .js-truncated-info.truncated-info.hidden
- %span<
- Showing last
- %span.js-truncated-info-size><
- KiB of log
+ .js-truncated-info.truncated-info.hidden<
+ Showing last
+ %span.js-truncated-info-size.truncated-info-size><
+ KiB of log -
+ %a.js-raw-link.raw-link{ :href => raw_namespace_project_build_path(@project.namespace, @project, @build) }>< Complete Raw
%code.bash.js-build-output
.build-loader-animation.js-build-refresh
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index 4700b7a9a45..2c3fd1fcd4d 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -36,7 +36,7 @@
= icon('warning', class: 'text-warning has-tooltip', title: 'Job is stuck. Check runners.')
- if retried
- = icon('refresh', class: 'text-warning has-tooltip', title: 'Job was retried')
+ = icon('spinner', class: 'text-warning has-tooltip', title: 'Job was retried')
.label-container
- if job.tags.any?
diff --git a/app/views/projects/diffs/_content.html.haml b/app/views/projects/diffs/_content.html.haml
index 5c38b5ad9c0..438a98c3e95 100644
--- a/app/views/projects/diffs/_content.html.haml
+++ b/app/views/projects/diffs/_content.html.haml
@@ -4,7 +4,7 @@
- if diff_file.too_large?
.nothing-here-block This diff could not be displayed because it is too large.
- elsif blob.only_display_raw?
- .nothing-here-block This file is too large to display.
+ .nothing-here-block The file could not be displayed because it is too large.
- elsif blob_text_viewable?(blob)
- if !project.repository.diffable?(blob)
.nothing-here-block This diff was suppressed by a .gitattributes entry.
diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml
index f920f359de2..45c95f7ab6a 100644
--- a/app/views/projects/diffs/_parallel_view.html.haml
+++ b/app/views/projects/diffs/_parallel_view.html.haml
@@ -5,8 +5,7 @@
- left = line[:left]
- right = line[:right]
- last_line = right.new_pos if right
- - unless @diff_notes_disabled
- - discussions_left, discussions_right = parallel_diff_discussions(left, right, diff_file)
+ - discussions_left, discussions_right = parallel_diff_discussions(left, right, diff_file)
%tr.line_holder.parallel
- if left
- case left.type
diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml
index ebd1a914ee7..5f3968b6709 100644
--- a/app/views/projects/diffs/_text_file.html.haml
+++ b/app/views/projects/diffs/_text_file.html.haml
@@ -4,11 +4,10 @@
%a.show-suppressed-diff.js-show-suppressed-diff Changes suppressed. Click to show.
%table.text-file.code.js-syntax-highlight{ data: diff_view_data, class: too_big ? 'hide' : '' }
- - discussions = @grouped_diff_discussions unless @diff_notes_disabled
= render partial: "projects/diffs/line",
collection: diff_file.highlighted_diff_lines,
as: :line,
- locals: { diff_file: diff_file, discussions: discussions }
+ locals: { diff_file: diff_file, discussions: @grouped_diff_discussions }
- if !diff_file.new_file && !diff_file.deleted_file && diff_file.highlighted_diff_lines.any?
- last_line = diff_file.highlighted_diff_lines.last
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 885795ccb5c..fcbd8829595 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -79,4 +79,5 @@
= render 'shared/issuable/sidebar', issuable: @issue
+= page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('issue_show')
diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml
index a80a07b52e6..7f0059cdcda 100644
--- a/app/views/projects/labels/edit.html.haml
+++ b/app/views/projects/labels/edit.html.haml
@@ -1,6 +1,6 @@
- @no_container = true
- page_title "Edit", @label.name, "Labels"
-= render "projects/issues/head"
+= render "shared/mr_head"
%div{ class: container_class }
%h3.page-title
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index 29f861c09c6..fc72c4fb635 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -1,7 +1,7 @@
- @no_container = true
- page_title "Labels"
- hide_class = ''
-= render "projects/issues/head"
+= render "shared/mr_head"
- if @labels.exists? || @prioritized_labels.exists?
%div{ class: container_class }
diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml
index f0d9be744d1..8f6c085a361 100644
--- a/app/views/projects/labels/new.html.haml
+++ b/app/views/projects/labels/new.html.haml
@@ -1,6 +1,6 @@
- @no_container = true
- page_title "New Label"
-= render "projects/issues/head"
+= render "shared/mr_head"
%div{ class: container_class }
%h3.page-title
diff --git a/app/views/projects/merge_requests/_head.html.haml b/app/views/projects/merge_requests/_head.html.haml
new file mode 100644
index 00000000000..b7f73fe5339
--- /dev/null
+++ b/app/views/projects/merge_requests/_head.html.haml
@@ -0,0 +1,21 @@
+= 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(controller: :merge_requests) do
+ = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests' do
+ %span
+ List
+
+ - if project_nav_tab? :labels
+ = nav_link(controller: :labels) do
+ = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do
+ %span
+ Labels
+
+ - if project_nav_tab? :milestones
+ = nav_link(controller: :milestones) do
+ = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do
+ %span
+ Milestones
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index 03069804c86..da79ca2ee75 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -46,7 +46,7 @@
-# This tab is always loaded via AJAX
- if @pipelines.any?
#pipelines.pipelines.tab-pane
- = render 'projects/merge_requests/show/pipelines', endpoint: url_for(params.merge(format: :json))
+ = render 'projects/merge_requests/show/pipelines', endpoint: url_for(params.merge(format: :json)), disable_initialization: true
.mr-loading-status
= spinner
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index 64f17ab34b1..6bf0035e051 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -2,6 +2,9 @@
- @bulk_edit = can?(current_user, :admin_merge_request, @project)
- page_title "Merge Requests"
+- unless @project.default_issues_tracker?
+ = content_for :sub_nav do
+ = render "projects/merge_requests/head"
= render 'projects/last_push'
- content_for :page_specific_javascripts do
diff --git a/app/views/projects/merge_requests/show/_pipelines.html.haml b/app/views/projects/merge_requests/show/_pipelines.html.haml
index de4aa255bbd..2f1dbe87619 100644
--- a/app/views/projects/merge_requests/show/_pipelines.html.haml
+++ b/app/views/projects/merge_requests/show/_pipelines.html.haml
@@ -1,3 +1,4 @@
- endpoint_path = local_assigns[:endpoint] || pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, format: :json)
+- disable_initialization = local_assigns.fetch(:disable_initialization, false)
-= render 'projects/commit/pipelines_list', endpoint: endpoint_path
+= render 'projects/commit/pipelines_list', endpoint: endpoint_path, disable_initialization: disable_initialization
diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml
index 74a7b1dc498..547be78992e 100644
--- a/app/views/projects/merge_requests/show/_versions.html.haml
+++ b/app/views/projects/merge_requests/show/_versions.html.haml
@@ -72,13 +72,16 @@
= link_to namespace_project_compare_path(@project.namespace, @project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do
new commits
from
- %code= @merge_request.target_branch
+ = succeed '.' do
+ %code= @merge_request.target_branch
- - unless @merge_request_diff.latest? && !@start_sha
+ - if @diff_notes_disabled
.comments-disabled-notif.content-block
= icon('info-circle')
- if @start_sha
Comments are disabled because you're comparing two versions of this merge request.
- else
- Comments are disabled because you're viewing an old version of this merge request.
- = link_to 'Show latest version', diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-sm'
+ Discussions on this version of the merge request are displayed but comment creation is disabled.
+
+ .pull-right
+ = link_to 'Show latest version', diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-sm'
diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml
index cb117d1908c..4cbd22150c7 100644
--- a/app/views/projects/merge_requests/widget/open/_accept.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml
@@ -18,7 +18,7 @@
Select merge moment
%ul.js-merge-dropdown.dropdown-menu.dropdown-menu-right{ role: 'menu' }
%li
- = link_to "#", class: "merge_when_pipeline_succeeds" do
+ = link_to "#", class: "merge-when-pipeline-succeeds" do
= icon('check fw')
Merge when pipeline succeeds
%li
diff --git a/app/views/projects/milestones/edit.html.haml b/app/views/projects/milestones/edit.html.haml
index 55b0b837c6d..e57a76dbfd2 100644
--- a/app/views/projects/milestones/edit.html.haml
+++ b/app/views/projects/milestones/edit.html.haml
@@ -1,6 +1,6 @@
- @no_container = true
- page_title "Edit", @milestone.title, "Milestones"
-= render "projects/issues/head"
+= render "shared/mr_head"
%div{ class: container_class }
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index 8e85b2e8a20..e1096bd1d67 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -1,6 +1,6 @@
- @no_container = true
- page_title 'Milestones'
-= render 'projects/issues/head'
+= render "shared/mr_head"
%div{ class: container_class }
.top-area
diff --git a/app/views/projects/milestones/new.html.haml b/app/views/projects/milestones/new.html.haml
index cda093ade81..586eb909afa 100644
--- a/app/views/projects/milestones/new.html.haml
+++ b/app/views/projects/milestones/new.html.haml
@@ -1,6 +1,6 @@
- @no_container = true
- page_title "New Milestone"
-= render "projects/issues/head"
+= render "shared/mr_head"
%div{ class: container_class }
%h3.page-title
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 8b62b156853..a173117984d 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -1,7 +1,7 @@
- @no_container = true
- page_title @milestone.title, "Milestones"
- page_description @milestone.description
-= render "projects/issues/head"
+= render "shared/mr_head"
%div{ class: container_class }
.detail-page-header.milestone-page-header
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index c12c05eeb73..7cf604bb772 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -12,19 +12,21 @@
= image_tag avatar_icon(note.author), alt: '', class: 'avatar s40'
.timeline-content
.note-header
- %a.visible-xs{ href: user_path(note.author) }
- = note.author.to_reference
- = link_to_member(note.project, note.author, avatar: false, extra_class: 'hidden-xs')
- .note-headline-light
- %span.hidden-xs
- = note.author.to_reference
- - unless note.system
- commented
- - if note.system
- %span.system-note-message
- = note.redacted_note_html
- %a{ href: "##{dom_id(note)}" }
- = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago')
+ .note-header-info
+ %a{ href: user_path(note.author) }
+ %span.hidden-xs
+ = sanitize(note.author.name)
+ %span.note-headline-light
+ = note.author.to_reference
+ %span.note-headline-light
+ %span.note-headline-meta
+ - unless note.system
+ commented
+ - if note.system
+ %span.system-note-message
+ = note.redacted_note_html
+ %a{ href: "##{dom_id(note)}" }
+ = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago')
- unless note.system?
.note-actions
- access = note_max_access_for_user(note)
@@ -52,15 +54,15 @@
":aria-label" => "buttonText",
"@click" => "resolve",
":title" => "buttonText",
- "v-show" => "!loading",
":ref" => "'button'" }
- = icon("spin spinner", "v-show" => "loading")
- = render "shared/icons/icon_status_success.svg"
+ = icon("spin spinner", "v-show" => "loading", class: 'loading')
+ %div{ 'v-show' => '!loading' }= render "shared/icons/icon_status_success.svg"
- if current_user
- if note.emoji_awardable?
- = link_to '#', title: 'Award Emoji', class: 'note-action-button note-emoji-button js-add-award js-note-emoji', data: { position: 'right' } do
+ - user_authored = note.user_authored?(current_user)
+ = link_to '#', title: 'Award Emoji', class: "note-action-button note-emoji-button js-add-award js-note-emoji #{'js-user-authored' if user_authored}", data: { position: 'right' } do
= icon('spinner spin')
%span{ class: "link-highlight award-control-icon-neutral" }= custom_icon('emoji_slightly_smiling_face')
%span{ class: "link-highlight award-control-icon-positive" }= custom_icon('emoji_smiley')
diff --git a/app/views/projects/registry/repositories/_image.html.haml b/app/views/projects/registry/repositories/_image.html.haml
index d183ce34a3a..8bc78f8d018 100644
--- a/app/views/projects/registry/repositories/_image.html.haml
+++ b/app/views/projects/registry/repositories/_image.html.haml
@@ -4,7 +4,7 @@
= icon('chevron-down', 'aria-hidden': 'true')
= escape_once(image.path)
- = clipboard_button(clipboard_text: "docker pull #{image.path}")
+ = clipboard_button(clipboard_text: "docker pull #{image.location}")
.controls.hidden-xs.pull-right
= link_to namespace_project_container_registry_path(@project.namespace, @project, image),
diff --git a/app/views/projects/registry/repositories/_tag.html.haml b/app/views/projects/registry/repositories/_tag.html.haml
index 854b7d0ebf7..378a23f07e6 100644
--- a/app/views/projects/registry/repositories/_tag.html.haml
+++ b/app/views/projects/registry/repositories/_tag.html.haml
@@ -1,7 +1,7 @@
%tr.tag
%td
= escape_once(tag.name)
- = clipboard_button(text: "docker pull #{tag.path}")
+ = clipboard_button(text: "docker pull #{tag.location}")
%td
- if tag.revision
%span.has-tooltip{ title: "#{tag.revision}" }
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index edfe6da1816..fd7bd21677c 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -12,8 +12,8 @@
= render "projects/last_push"
= render "home_panel"
-- if current_user && can?(current_user, :download_code, @project)
- %nav.project-stats.limit-container-width{ class: container_class }
+- if can?(current_user, :download_code, @project)
+ %nav.project-stats{ class: container_class }
%ul.nav
%li
= link_to project_files_path(@project) do
@@ -74,11 +74,11 @@
Set up auto deploy
- if @repository.commit
- .limit-container-width{ class: container_class }
+ %div{ class: container_class }
.project-last-commit
= render 'projects/last_commit', commit: @repository.commit, ref: current_ref, project: @project
-.limit-container-width{ class: container_class }
+%div{ class: container_class }
- if @project.archived?
.text-warning.center.prepend-top-20
%p
diff --git a/app/views/projects/snippets/edit.html.haml b/app/views/projects/snippets/edit.html.haml
index fb39028529d..24b92094b7d 100644
--- a/app/views/projects/snippets/edit.html.haml
+++ b/app/views/projects/snippets/edit.html.haml
@@ -1,4 +1,4 @@
-- page_title "Edit", @snippet.title, "Snippets"
+- page_title "Edit", "#{@snippet.title} (#{@snippet.to_reference})", "Snippets"
%h3.page-title
Edit Snippet
diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml
index e35385f4cab..7c6be003d4c 100644
--- a/app/views/projects/snippets/show.html.haml
+++ b/app/views/projects/snippets/show.html.haml
@@ -1,4 +1,4 @@
-- page_title @snippet.title, "Snippets"
+- page_title "#{@snippet.title} (#{@snippet.to_reference})", "Snippets"
= render 'shared/snippets/header'
diff --git a/app/views/projects/variables/_table.html.haml b/app/views/projects/variables/_table.html.haml
index c7cebf45160..0ce597dcf21 100644
--- a/app/views/projects/variables/_table.html.haml
+++ b/app/views/projects/variables/_table.html.haml
@@ -14,7 +14,7 @@
%tr
%td.variable-key= variable.key
%td.variable-value{ "data-value" => variable.value }******
- %td
+ %td.variable-menu
= link_to namespace_project_variable_path(@project.namespace, @project, variable), class: "btn btn-transparent btn-variable-edit" do
%span.sr-only
Update
diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml
index 86178257af8..6a578dbf640 100644
--- a/app/views/projects/wikis/_main_links.html.haml
+++ b/app/views/projects/wikis/_main_links.html.haml
@@ -5,5 +5,5 @@
= link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn" do
Page history
- if can?(current_user, :create_wiki, @project) && @page.latest?
- = link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn" do
+ = link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn js-wiki-edit" do
Edit
diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml
index e010f21de5a..fc4385865a4 100644
--- a/app/views/search/results/_issue.html.haml
+++ b/app/views/search/results/_issue.html.haml
@@ -3,6 +3,8 @@
= confidential_icon(issue)
= link_to [issue.project.namespace.becomes(Namespace), issue.project, issue] do
%span.term.str-truncated= issue.title
+ - if issue.closed?
+ %span.label.label-danger.prepend-left-5 Closed
.pull-right ##{issue.iid}
- if issue.description.present?
.description.term
@@ -10,6 +12,3 @@
= search_md_sanitize(issue, :description)
%span.light
#{issue.project.name_with_namespace}
- - if issue.closed?
- .pull-right
- %span.label.label-danger Closed
diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml
index 2e6adf3027c..9b583285d02 100644
--- a/app/views/search/results/_merge_request.html.haml
+++ b/app/views/search/results/_merge_request.html.haml
@@ -2,6 +2,10 @@
%h4
= link_to [merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request] do
%span.term.str-truncated= merge_request.title
+ - if merge_request.merged?
+ %span.label.label-primary.prepend-left-5 Merged
+ - elsif merge_request.closed?
+ %span.label.label-danger.prepend-left-5 Closed
.pull-right= merge_request.to_reference
- if merge_request.description.present?
.description.term
@@ -9,8 +13,3 @@
= search_md_sanitize(merge_request, :description)
%span.light
#{merge_request.project.name_with_namespace}
- .pull-right
- - if merge_request.merged?
- %span.label.label-primary Merged
- - elsif merge_request.closed?
- %span.label.label-danger Closed
diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml
index 8869d510aef..90ae3f06a98 100644
--- a/app/views/shared/_group_form.html.haml
+++ b/app/views/shared/_group_form.html.haml
@@ -1,12 +1,8 @@
+- content_for :page_specific_javascripts do
+ = page_specific_javascript_bundle_tag('group')
- parent = GroupFinder.new(current_user).execute(id: params[:parent_id] || @group.parent_id)
- group_path = root_url
- group_path << parent.full_path + '/' if parent
-- if @group.persisted?
- .form-group
- = f.label :name, class: 'control-label' do
- Group name
- .col-sm-10
- = f.text_field :name, placeholder: 'open-source', class: 'form-control'
.form-group
= f.label :path, class: 'control-label' do
@@ -20,7 +16,7 @@
= f.text_field :path, placeholder: 'open-source', class: 'form-control',
autofocus: local_assigns[:autofocus] || false, required: true,
pattern: Gitlab::Regex::NAMESPACE_REGEX_STR_JS,
- title: 'Please choose a group name with no special characters.',
+ title: 'Please choose a group path with no special characters.',
"data-bind-in" => "#{'create_chat_team' if Gitlab.config.mattermost.enabled}"
- if parent
= f.hidden_field :parent_id, value: parent.id
@@ -33,6 +29,14 @@
%li It will change web url for access group and group projects.
%li It will change the git path to repositories under this group.
+.form-group.group-name-holder
+ = f.label :name, class: 'control-label' do
+ Group name
+ .col-sm-10
+ = f.text_field :name, class: 'form-control',
+ required: true,
+ title: 'You can choose a descriptive name different from the path.'
+
.form-group.group-description-holder
= f.label :description, class: 'control-label'
.col-sm-10
diff --git a/app/views/shared/_import_form.html.haml b/app/views/shared/_import_form.html.haml
index 54b5ae2402e..1c7c73be933 100644
--- a/app/views/shared/_import_form.html.haml
+++ b/app/views/shared/_import_form.html.haml
@@ -2,7 +2,7 @@
= f.label :import_url, class: 'control-label' do
%span Git repository URL
.col-sm-10
- = f.text_field :import_url, autocomplete: 'off', class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git', disabled: true
+ = f.text_field :import_url, autocomplete: 'off', class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git'
.well.prepend-top-20
%ul
diff --git a/app/views/shared/_mr_head.html.haml b/app/views/shared/_mr_head.html.haml
new file mode 100644
index 00000000000..4211ec6351d
--- /dev/null
+++ b/app/views/shared/_mr_head.html.haml
@@ -0,0 +1,4 @@
+- if @project.default_issues_tracker?
+ = render "projects/issues/head"
+- else
+ = render "projects/merge_requests/head"
diff --git a/app/views/shared/_user_callout.html.haml b/app/views/shared/_user_callout.html.haml
index 8f1293adcb1..8308baa7829 100644
--- a/app/views/shared/_user_callout.html.haml
+++ b/app/views/shared/_user_callout.html.haml
@@ -3,12 +3,11 @@
%button.btn.btn-default.close.js-close-callout{ type: 'button',
'aria-label' => 'Dismiss customize experience box' }
= icon('times', class: 'dismiss-icon', 'aria-hidden' => 'true')
- .row
- .col-sm-3.col-xs-12.svg-container
- = custom_icon('icon_customization')
- .col-sm-8.col-xs-12.inner-content
- %h4
- Customize your experience
- %p
- Change syntax themes, default project pages, and more in preferences.
- = link_to 'Check it out', profile_preferences_path, class: 'btn btn-default js-close-callout'
+ .svg-container
+ = custom_icon('icon_customization')
+ .user-callout-copy
+ %h4
+ Customize your experience
+ %p
+ Change syntax themes, default project pages, and more in preferences.
+ = link_to 'Check it out', profile_preferences_path, class: 'btn btn-primary js-close-callout'
diff --git a/app/views/shared/empty_states/_issues.html.haml b/app/views/shared/empty_states/_issues.html.haml
index 7a7e3d46796..c229d18903f 100644
--- a/app/views/shared/empty_states/_issues.html.haml
+++ b/app/views/shared/empty_states/_issues.html.haml
@@ -16,6 +16,8 @@
Also, issues are searchable and filterable.
- if project_select_button
= render 'shared/new_project_item_select', path: 'issues/new', label: 'New issue'
+ = link_to 'New issue', button_path, class: 'btn btn-new', title: 'New issue', id: 'new_issue_link'
- else
- %h4 There are no issues to show.
- = link_to 'New issue', button_path, class: 'btn btn-new', title: 'New issue', id: 'new_issue_link'
+ .text-center
+ %h4 There are no issues to show.
+ = link_to 'New issue', button_path, class: 'btn btn-new', title: 'New issue', id: 'new_issue_link'
diff --git a/app/views/shared/icons/_icon_merged.svg b/app/views/shared/icons/_icon_merged.svg
index d8f96558bea..43d591daefa 100644
--- a/app/views/shared/icons/_icon_merged.svg
+++ b/app/views/shared/icons/_icon_merged.svg
@@ -1 +1 @@
-<svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg"><path d="M0 7c0-3.866 3.142-7 7-7 3.866 0 7 3.142 7 7 0 3.866-3.142 7-7 7-3.866 0-7-3.142-7-7z"/><path d="M1 7c0 3.309 2.69 6 6 6 3.309 0 6-2.69 6-6 0-3.309-2.69-6-6-6-3.309 0-6 2.69-6 6z" fill="#FFF"/><path d="M9.427 6.523a.932.932 0 0 0-.808.489v-.01c-.49-.01-1.059-.172-1.46-.489-.35-.278-.7-.772-.882-1.17a.964.964 0 0 0 .35-.744.943.943 0 0 0-.934-.959c-.518 0-.933.432-.933.964 0 .35.191.662.467.825v3.147a.97.97 0 0 0-.467.825c0 .532.415.959.933.959a.943.943 0 0 0 .934-.96.965.965 0 0 0-.467-.824V6.844c.313.336.672.61 1.073.81.402.202.948.303 1.386.308v-.01c.168.293.467.49.808.49a.943.943 0 0 0 .933-.96.943.943 0 0 0-.933-.96z"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="m2 3c.552 0 1-.448 1-1 0-.552-.448-1-1-1-.552 0-1 .448-1 1 0 .552.448 1 1 1m.761.85c.154 2.556 1.987 4.692 4.45 5.255.328-.655 1.01-1.105 1.789-1.105 1.105 0 2 .895 2 2 0 1.105-.895 2-2 2-.89 0-1.645-.582-1.904-1.386-1.916-.376-3.548-1.5-4.596-3.044v4.493c.863.222 1.5 1.01 1.5 1.937 0 1.105-.895 2-2 2-1.105 0-2-.895-2-2 0-.74.402-1.387 1-1.732v-8.535c-.598-.346-1-.992-1-1.732 0-1.105.895-2 2-2 1.105 0 2 .895 2 2 0 .835-.512 1.551-1.239 1.85m6.239 7.15c.552 0 1-.448 1-1 0-.552-.448-1-1-1-.552 0-1 .448-1 1 0 .552.448 1 1 1m-7 4c.552 0 1-.448 1-1 0-.552-.448-1-1-1-.552 0-1 .448-1 1 0 .552.448 1 1 1" transform="translate(3)"/></svg>
diff --git a/app/views/shared/icons/_trash_o.svg b/app/views/shared/icons/_icon_trash_o.svg
index 0d7a91ab536..0d7a91ab536 100644
--- a/app/views/shared/icons/_trash_o.svg
+++ b/app/views/shared/icons/_icon_trash_o.svg
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index b447996a8ab..f1350169bbe 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -12,13 +12,14 @@
class: "check_all_issues left"
.issues-other-filters.filtered-search-wrapper
.filtered-search-box
- = dropdown_tag(content_tag(:i, '', class: 'fa fa-history'),
- options: { wrapper_class: "filtered-search-history-dropdown-wrapper",
- toggle_class: "filtered-search-history-dropdown-toggle-button",
- dropdown_class: "filtered-search-history-dropdown",
- content_class: "filtered-search-history-dropdown-content",
- title: "Recent searches" }) do
- .js-filtered-search-history-dropdown
+ - if type != :boards_modal && type != :boards
+ = dropdown_tag(content_tag(:i, '', class: 'fa fa-history'),
+ options: { wrapper_class: "filtered-search-history-dropdown-wrapper",
+ toggle_class: "filtered-search-history-dropdown-toggle-button",
+ dropdown_class: "filtered-search-history-dropdown",
+ content_class: "filtered-search-history-dropdown-content",
+ title: "Recent searches" }) do
+ .js-filtered-search-history-dropdown
.filtered-search-box-input-container
.scroll-container
%ul.tokens-container.list-unstyled
diff --git a/app/views/snippets/edit.html.haml b/app/views/snippets/edit.html.haml
index 915bf98eb3e..18ebeb78f87 100644
--- a/app/views/snippets/edit.html.haml
+++ b/app/views/snippets/edit.html.haml
@@ -1,4 +1,4 @@
-- page_title "Edit", @snippet.title, "Snippets"
+- page_title "Edit", "#{@snippet.title} (#{@snippet.to_reference})", "Snippets"
%h3.page-title
Edit Snippet
%hr
diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml
index da9fb755a36..e5711ca79c7 100644
--- a/app/views/snippets/show.html.haml
+++ b/app/views/snippets/show.html.haml
@@ -1,4 +1,4 @@
-- page_title @snippet.title, "Snippets"
+- page_title "#{@snippet.title} (#{@snippet.to_reference})", "Snippets"
= render 'shared/snippets/header'
diff --git a/app/workers/build_coverage_worker.rb b/app/workers/build_coverage_worker.rb
index def0ab1dde1..f7ae996bb17 100644
--- a/app/workers/build_coverage_worker.rb
+++ b/app/workers/build_coverage_worker.rb
@@ -3,7 +3,6 @@ class BuildCoverageWorker
include BuildQueue
def perform(build_id)
- Ci::Build.find_by(id: build_id)
- .try(:update_coverage)
+ Ci::Build.find_by(id: build_id)&.update_coverage
end
end
diff --git a/changelogs/unreleased/2120-issues-search-bar-not-picking-up.yml b/changelogs/unreleased/2120-issues-search-bar-not-picking-up.yml
new file mode 100644
index 00000000000..706609b7baf
--- /dev/null
+++ b/changelogs/unreleased/2120-issues-search-bar-not-picking-up.yml
@@ -0,0 +1,4 @@
+---
+title: Fix filtered search input width for IE
+merge_request:
+author:
diff --git a/changelogs/unreleased/22714-update-all-instances-of-fa-refresh.yml b/changelogs/unreleased/22714-update-all-instances-of-fa-refresh.yml
new file mode 100644
index 00000000000..ad7c011933f
--- /dev/null
+++ b/changelogs/unreleased/22714-update-all-instances-of-fa-refresh.yml
@@ -0,0 +1,4 @@
+---
+title: Update all instances of the old loading icon
+merge_request: 10490
+author: Andrew Torres
diff --git a/changelogs/unreleased/27655-clear-emoji-search-after-selection.yml b/changelogs/unreleased/27655-clear-emoji-search-after-selection.yml
new file mode 100644
index 00000000000..5fd02696323
--- /dev/null
+++ b/changelogs/unreleased/27655-clear-emoji-search-after-selection.yml
@@ -0,0 +1,4 @@
+---
+title: Clear emoji search in awards menu after picking emoji
+merge_request:
+author:
diff --git a/changelogs/unreleased/27729-improve-webpack-dev-environment.yml b/changelogs/unreleased/27729-improve-webpack-dev-environment.yml
new file mode 100644
index 00000000000..d04ea70ab1c
--- /dev/null
+++ b/changelogs/unreleased/27729-improve-webpack-dev-environment.yml
@@ -0,0 +1,4 @@
+---
+title: Add webpack_bundle_tag helper to improve non-localhost GDK configurations
+merge_request: 10604
+author:
diff --git a/changelogs/unreleased/28017-separate-ce-params-on-api.yml b/changelogs/unreleased/28017-separate-ce-params-on-api.yml
new file mode 100644
index 00000000000..039a8d207b0
--- /dev/null
+++ b/changelogs/unreleased/28017-separate-ce-params-on-api.yml
@@ -0,0 +1,4 @@
+---
+title: Separate CE params on Grape API
+merge_request:
+author:
diff --git a/changelogs/unreleased/28575-expand-collapse-look.yml b/changelogs/unreleased/28575-expand-collapse-look.yml
new file mode 100644
index 00000000000..d8943316300
--- /dev/null
+++ b/changelogs/unreleased/28575-expand-collapse-look.yml
@@ -0,0 +1,4 @@
+---
+title: Expand/collapse button -> Change to make it look like a toggle
+merge_request: 10720
+author: Jacopo Beschi @jacopo-beschi
diff --git a/changelogs/unreleased/29056-backport-ee-cleanup-database-file.yml b/changelogs/unreleased/29056-backport-ee-cleanup-database-file.yml
new file mode 100644
index 00000000000..0ebb9d57611
--- /dev/null
+++ b/changelogs/unreleased/29056-backport-ee-cleanup-database-file.yml
@@ -0,0 +1,4 @@
+---
+title: Turns true value and false value database methods from instance to class methods
+merge_request: 10583
+author:
diff --git a/changelogs/unreleased/29595-customize-experience-callout.yml b/changelogs/unreleased/29595-customize-experience-callout.yml
new file mode 100644
index 00000000000..ec8393142c6
--- /dev/null
+++ b/changelogs/unreleased/29595-customize-experience-callout.yml
@@ -0,0 +1,4 @@
+---
+title: 29595 Update callout design
+merge_request:
+author:
diff --git a/changelogs/unreleased/29816-create-keyboard-shortcut-for-editing-wiki-page.yml b/changelogs/unreleased/29816-create-keyboard-shortcut-for-editing-wiki-page.yml
new file mode 100644
index 00000000000..a165c70a6d3
--- /dev/null
+++ b/changelogs/unreleased/29816-create-keyboard-shortcut-for-editing-wiki-page.yml
@@ -0,0 +1,4 @@
+---
+title: Add keyboard edit shotcut for wiki
+merge_request: 10245
+author: George Andrinopoulos
diff --git a/changelogs/unreleased/29866-navbar-counters.yml b/changelogs/unreleased/29866-navbar-counters.yml
deleted file mode 100644
index c67dff6cffa..00000000000
--- a/changelogs/unreleased/29866-navbar-counters.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add shortcuts and counters to MRs and issues in navbar
-merge_request:
-author:
diff --git a/changelogs/unreleased/30024-owner-can-t-initialize-git-repo-for-new-project-in-group.yml b/changelogs/unreleased/30024-owner-can-t-initialize-git-repo-for-new-project-in-group.yml
deleted file mode 100644
index c43d2732b9a..00000000000
--- a/changelogs/unreleased/30024-owner-can-t-initialize-git-repo-for-new-project-in-group.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Disable invalid service templates
-merge_request:
-author:
diff --git a/changelogs/unreleased/30349-create-users-build-service.yml b/changelogs/unreleased/30349-create-users-build-service.yml
new file mode 100644
index 00000000000..49b571f5646
--- /dev/null
+++ b/changelogs/unreleased/30349-create-users-build-service.yml
@@ -0,0 +1,4 @@
+---
+title: Implement Users::BuildService
+merge_request: 30349
+author: George Andrinopoulos
diff --git a/changelogs/unreleased/30457-expire-note-destroy.yml b/changelogs/unreleased/30457-expire-note-destroy.yml
new file mode 100644
index 00000000000..f5c89da68a9
--- /dev/null
+++ b/changelogs/unreleased/30457-expire-note-destroy.yml
@@ -0,0 +1,4 @@
+---
+title: Fix issue's note cache expiration after delete
+merge_request:
+author: mhasbini
diff --git a/changelogs/unreleased/30678-improve-dev-server-process.yml b/changelogs/unreleased/30678-improve-dev-server-process.yml
new file mode 100644
index 00000000000..efa2fc210e3
--- /dev/null
+++ b/changelogs/unreleased/30678-improve-dev-server-process.yml
@@ -0,0 +1,4 @@
+---
+title: Keep webpack-dev-server process functional across branch changes
+merge_request: 10581
+author:
diff --git a/changelogs/unreleased/30779-show-mr-subnav-issue-tracker.yml b/changelogs/unreleased/30779-show-mr-subnav-issue-tracker.yml
new file mode 100644
index 00000000000..59f8942911c
--- /dev/null
+++ b/changelogs/unreleased/30779-show-mr-subnav-issue-tracker.yml
@@ -0,0 +1,4 @@
+---
+title: Show sub-nav under Merge Requests when issue tracker is non-default.
+merge_request: 10658
+author:
diff --git a/changelogs/unreleased/31009-disable-test-settings-on-services-when-repository-is-empty.yml b/changelogs/unreleased/31009-disable-test-settings-on-services-when-repository-is-empty.yml
new file mode 100644
index 00000000000..6e43a032f20
--- /dev/null
+++ b/changelogs/unreleased/31009-disable-test-settings-on-services-when-repository-is-empty.yml
@@ -0,0 +1,4 @@
+---
+title: Disable test settings on chat notification services when repository is empty
+merge_request: 10759
+author:
diff --git a/changelogs/unreleased/6260-frontend-prevent-authored-votes.yml b/changelogs/unreleased/6260-frontend-prevent-authored-votes.yml
new file mode 100644
index 00000000000..82e852fa197
--- /dev/null
+++ b/changelogs/unreleased/6260-frontend-prevent-authored-votes.yml
@@ -0,0 +1,4 @@
+---
+title: 'Frontend prevent authored votes'
+merge_request: 6260
+author: Barthc
diff --git a/changelogs/unreleased/add-field-for-group-name.yml b/changelogs/unreleased/add-field-for-group-name.yml
new file mode 100644
index 00000000000..0fe511a4fa1
--- /dev/null
+++ b/changelogs/unreleased/add-field-for-group-name.yml
@@ -0,0 +1,4 @@
+---
+title: Add a name field to the group form
+merge_request: 9891
+author: Douglas Lovell
diff --git a/changelogs/unreleased/add-vue-loader.yml b/changelogs/unreleased/add-vue-loader.yml
new file mode 100644
index 00000000000..382ef61ff21
--- /dev/null
+++ b/changelogs/unreleased/add-vue-loader.yml
@@ -0,0 +1,4 @@
+---
+title: add support for .vue templates
+merge_request: 10517
+author:
diff --git a/changelogs/unreleased/bb_save_trace.yml b/changelogs/unreleased/bb_save_trace.yml
new file mode 100644
index 00000000000..6ff31f4f111
--- /dev/null
+++ b/changelogs/unreleased/bb_save_trace.yml
@@ -0,0 +1,5 @@
+---
+title: "[BB Importer] Save the error trace and the whole raw document to debug problems
+ easier"
+merge_request:
+author:
diff --git a/changelogs/unreleased/boards-done-add-tooltip.yml b/changelogs/unreleased/boards-done-add-tooltip.yml
new file mode 100644
index 00000000000..139f1efc8ee
--- /dev/null
+++ b/changelogs/unreleased/boards-done-add-tooltip.yml
@@ -0,0 +1,4 @@
+---
+title: Add tooltip to header of Done board
+merge_request: 10574
+author: Andy Brown
diff --git a/changelogs/unreleased/clean_carrierwave_tempfiles.yml b/changelogs/unreleased/clean_carrierwave_tempfiles.yml
new file mode 100644
index 00000000000..53fa69700ff
--- /dev/null
+++ b/changelogs/unreleased/clean_carrierwave_tempfiles.yml
@@ -0,0 +1,4 @@
+---
+title: Periodically clean up temporary upload files to recover storage space
+merge_request: 9466
+author: blackst0ne
diff --git a/changelogs/unreleased/dm-link-discussion-to-outdated-diff.yml b/changelogs/unreleased/dm-link-discussion-to-outdated-diff.yml
new file mode 100644
index 00000000000..d489bada7ea
--- /dev/null
+++ b/changelogs/unreleased/dm-link-discussion-to-outdated-diff.yml
@@ -0,0 +1,4 @@
+---
+title: Link to outdated diff in older MR version from outdated diff discussion
+merge_request:
+author:
diff --git a/changelogs/unreleased/dz-fix-project-view.yml b/changelogs/unreleased/dz-fix-project-view.yml
new file mode 100644
index 00000000000..647a1c96bd9
--- /dev/null
+++ b/changelogs/unreleased/dz-fix-project-view.yml
@@ -0,0 +1,4 @@
+---
+title: Change project view default for existing users and anonymous visitors to files+readme
+merge_request: 10498
+author:
diff --git a/changelogs/unreleased/dz-remove-repo-version.yml b/changelogs/unreleased/dz-remove-repo-version.yml
new file mode 100644
index 00000000000..f9e51a920f9
--- /dev/null
+++ b/changelogs/unreleased/dz-remove-repo-version.yml
@@ -0,0 +1,4 @@
+---
+title: Remove Repository#version method and tests
+merge_request: 10734
+author:
diff --git a/changelogs/unreleased/empty-task-list-alignment.yml b/changelogs/unreleased/empty-task-list-alignment.yml
new file mode 100644
index 00000000000..ca04e1cab5a
--- /dev/null
+++ b/changelogs/unreleased/empty-task-list-alignment.yml
@@ -0,0 +1,4 @@
+---
+title: Fixed alignment of empty task list items
+merge_request:
+author:
diff --git a/changelogs/unreleased/enforce-Ansi2html-output-encoding.yml b/changelogs/unreleased/enforce-Ansi2html-output-encoding.yml
new file mode 100644
index 00000000000..b1200548518
--- /dev/null
+++ b/changelogs/unreleased/enforce-Ansi2html-output-encoding.yml
@@ -0,0 +1,4 @@
+---
+title: Fix trace cannot be written due to encoding
+merge_request: 10758
+author:
diff --git a/changelogs/unreleased/fix-missing-capitalisation-buttons.yml b/changelogs/unreleased/fix-missing-capitalisation-buttons.yml
new file mode 100644
index 00000000000..b2c40483475
--- /dev/null
+++ b/changelogs/unreleased/fix-missing-capitalisation-buttons.yml
@@ -0,0 +1,4 @@
+---
+title: Fix missing capitalisation on views
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-preloading-merge_request_diff.yml b/changelogs/unreleased/fix-preloading-merge_request_diff.yml
new file mode 100644
index 00000000000..d38b6b0a707
--- /dev/null
+++ b/changelogs/unreleased/fix-preloading-merge_request_diff.yml
@@ -0,0 +1,4 @@
+---
+title: Fix bad query for PostgreSQL showing merge requests list
+merge_request: 10666
+author:
diff --git a/changelogs/unreleased/fix-project-visibility-setting.yml b/changelogs/unreleased/fix-project-visibility-setting.yml
new file mode 100644
index 00000000000..0fc219ccf52
--- /dev/null
+++ b/changelogs/unreleased/fix-project-visibility-setting.yml
@@ -0,0 +1,4 @@
+---
+title: Fix restricted project visibility setting available to users
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-trace-encoding.yml b/changelogs/unreleased/fix-trace-encoding.yml
new file mode 100644
index 00000000000..152610c43f5
--- /dev/null
+++ b/changelogs/unreleased/fix-trace-encoding.yml
@@ -0,0 +1,4 @@
+---
+title: Fix another case where trace does not have proper encoding set
+merge_request: 10728
+author:
diff --git a/changelogs/unreleased/fix-trace-seeking.yml b/changelogs/unreleased/fix-trace-seeking.yml
new file mode 100644
index 00000000000..b753df4bb43
--- /dev/null
+++ b/changelogs/unreleased/fix-trace-seeking.yml
@@ -0,0 +1,4 @@
+---
+title: Fix invalid encoding when showing some traces
+merge_request: 10681
+author:
diff --git a/changelogs/unreleased/fix_cache_expiration_in_repository.yml b/changelogs/unreleased/fix_cache_expiration_in_repository.yml
new file mode 100644
index 00000000000..5f34f2bd040
--- /dev/null
+++ b/changelogs/unreleased/fix_cache_expiration_in_repository.yml
@@ -0,0 +1,4 @@
+---
+title: Fix redundant cache expiration in Repository
+merge_request: 10575
+author: blackst0ne
diff --git a/changelogs/unreleased/fix_spaces_in_label_title.yml b/changelogs/unreleased/fix_spaces_in_label_title.yml
new file mode 100644
index 00000000000..51f07438edb
--- /dev/null
+++ b/changelogs/unreleased/fix_spaces_in_label_title.yml
@@ -0,0 +1,4 @@
+---
+title: Remove heading and trailing spaces from label's color and title
+merge_request: 10603
+author: blackst0ne
diff --git a/changelogs/unreleased/issues-empty-state-not-centered.yml b/changelogs/unreleased/issues-empty-state-not-centered.yml
new file mode 100644
index 00000000000..883125e28b1
--- /dev/null
+++ b/changelogs/unreleased/issues-empty-state-not-centered.yml
@@ -0,0 +1,4 @@
+---
+title: Centered issues empty state
+merge_request:
+author:
diff --git a/changelogs/unreleased/move-search-labels.yml b/changelogs/unreleased/move-search-labels.yml
new file mode 100644
index 00000000000..3a1d23d622e
--- /dev/null
+++ b/changelogs/unreleased/move-search-labels.yml
@@ -0,0 +1,4 @@
+---
+title: Move labels of search results from bottom to title
+merge_request: 10705
+author: dr
diff --git a/changelogs/unreleased/mr-widget-bug-fix.yml b/changelogs/unreleased/mr-widget-bug-fix.yml
new file mode 100644
index 00000000000..9af29d3927e
--- /dev/null
+++ b/changelogs/unreleased/mr-widget-bug-fix.yml
@@ -0,0 +1,5 @@
+---
+title: Fix MR widget bug that merged a MR when Merge when pipeline succeeds was clicked
+ via the dropdown
+merge_request: 10611
+author:
diff --git a/changelogs/unreleased/omnibus-gitlab-1993-check-shell-repositories-path-group-is-root.yml b/changelogs/unreleased/omnibus-gitlab-1993-check-shell-repositories-path-group-is-root.yml
new file mode 100644
index 00000000000..3b9284258cb
--- /dev/null
+++ b/changelogs/unreleased/omnibus-gitlab-1993-check-shell-repositories-path-group-is-root.yml
@@ -0,0 +1,4 @@
+---
+title: "Make the `gitlab:gitlab_shell:check` task check that the repositories storage path are owned by the `root` group"
+merge_request:
+author:
diff --git a/changelogs/unreleased/pms-lighter-colors.yml b/changelogs/unreleased/pms-lighter-colors.yml
new file mode 100644
index 00000000000..958d4bc0ac0
--- /dev/null
+++ b/changelogs/unreleased/pms-lighter-colors.yml
@@ -0,0 +1,4 @@
+---
+title: Add lighter colors and fix existing light colors
+merge_request: 10690
+author:
diff --git a/changelogs/unreleased/remove-double-newline-for-single-attachments.yml b/changelogs/unreleased/remove-double-newline-for-single-attachments.yml
new file mode 100644
index 00000000000..98a28e1ede1
--- /dev/null
+++ b/changelogs/unreleased/remove-double-newline-for-single-attachments.yml
@@ -0,0 +1,4 @@
+---
+title: Only add newlines between multiple uploads
+merge_request: 10545
+author:
diff --git a/changelogs/unreleased/remove_is_admin.yml b/changelogs/unreleased/remove_is_admin.yml
new file mode 100644
index 00000000000..f6baf1942de
--- /dev/null
+++ b/changelogs/unreleased/remove_is_admin.yml
@@ -0,0 +1,4 @@
+---
+title: Remove the User#is_admin? method
+merge_request: 10520
+author: blackst0ne
diff --git a/changelogs/unreleased/reset-new-branch-button.yml b/changelogs/unreleased/reset-new-branch-button.yml
new file mode 100644
index 00000000000..318ee46298f
--- /dev/null
+++ b/changelogs/unreleased/reset-new-branch-button.yml
@@ -0,0 +1,4 @@
+---
+title: Reset New branch button when issue state changes
+merge_request: 5962
+author: winniehell
diff --git a/changelogs/unreleased/sh-fix-ssh-keys-with-spaces.yml b/changelogs/unreleased/sh-fix-ssh-keys-with-spaces.yml
deleted file mode 100644
index fe75d7e1156..00000000000
--- a/changelogs/unreleased/sh-fix-ssh-keys-with-spaces.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Handle SSH keys that have multiple spaces between each marker
-merge_request:
-author:
diff --git a/changelogs/unreleased/siemens-gitlab-ce-fix-subgroup-hide-button.yml b/changelogs/unreleased/siemens-gitlab-ce-fix-subgroup-hide-button.yml
new file mode 100644
index 00000000000..716311c7582
--- /dev/null
+++ b/changelogs/unreleased/siemens-gitlab-ce-fix-subgroup-hide-button.yml
@@ -0,0 +1,4 @@
+---
+title: Hide new subgroup button if user has no permission to create one
+merge_request: 10627
+author:
diff --git a/changelogs/unreleased/spec_for_schema.yml b/changelogs/unreleased/spec_for_schema.yml
new file mode 100644
index 00000000000..7ea0b8672ce
--- /dev/null
+++ b/changelogs/unreleased/spec_for_schema.yml
@@ -0,0 +1,4 @@
+---
+title: Add spec for schema.rb
+merge_request: 10580
+author: blackst0ne
diff --git a/changelogs/unreleased/use-hashie-forbidden_attributes.yml b/changelogs/unreleased/use-hashie-forbidden_attributes.yml
new file mode 100644
index 00000000000..4f429b03a0d
--- /dev/null
+++ b/changelogs/unreleased/use-hashie-forbidden_attributes.yml
@@ -0,0 +1,4 @@
+---
+title: Add hashie-forbidden_attributes gem
+merge_request: 10579
+author: Andy Brown
diff --git a/changelogs/unreleased/user-activity-scroll-bar.yml b/changelogs/unreleased/user-activity-scroll-bar.yml
new file mode 100644
index 00000000000..97cccee42cb
--- /dev/null
+++ b/changelogs/unreleased/user-activity-scroll-bar.yml
@@ -0,0 +1,4 @@
+---
+title: Fix preemptive scroll bar on user activity calendar.
+merge_request: !10636
+author:
diff --git a/changelogs/unreleased/zj-api-fix-build-events.yml b/changelogs/unreleased/zj-api-fix-build-events.yml
new file mode 100644
index 00000000000..7700d8dcd22
--- /dev/null
+++ b/changelogs/unreleased/zj-api-fix-build-events.yml
@@ -0,0 +1,4 @@
+---
+title: "Bugfix: POST /projects/:id/hooks and PUT /projects/:id/hook/:hook_id no longer ignore the the job_events param in the V4 API"
+merge_request: 10586
+author:
diff --git a/changelogs/unreleased/zj-fk-ci-triggers.yml b/changelogs/unreleased/zj-fk-ci-triggers.yml
new file mode 100644
index 00000000000..9fe708b25c0
--- /dev/null
+++ b/changelogs/unreleased/zj-fk-ci-triggers.yml
@@ -0,0 +1,4 @@
+---
+title: Add foreign key for ci_trigger_requests on ci_triggers
+merge_request: 10537
+author:
diff --git a/config/dependency_decisions.yml b/config/dependency_decisions.yml
index fdba1f6541e..59c7050a14d 100644
--- a/config/dependency_decisions.yml
+++ b/config/dependency_decisions.yml
@@ -344,3 +344,57 @@
:why: https://github.com/nodeca/pako/blob/master/LICENSE
:versions: []
:when: 2017-04-05 10:43:45.897720000 Z
+- - :approve
+ - caniuse-db
+ - :who: Mike Greiling
+ :why: https://github.com/Fyrd/caniuse/blob/master/LICENSE
+ :versions: []
+ :when: 2017-04-07 16:05:14.185549000 Z
+- - :approve
+ - domelementtype
+ - :who: Mike Greiling
+ :why: https://github.com/fb55/domelementtype/blob/master/LICENSE
+ :versions: []
+ :when: 2017-04-07 16:19:17.992640000 Z
+- - :approve
+ - domhandler
+ - :who: Mike Greiling
+ :why: https://github.com/fb55/domhandler/blob/master/LICENSE
+ :versions: []
+ :when: 2017-04-07 16:19:19.628953000 Z
+- - :approve
+ - domutils
+ - :who: Mike Greiling
+ :why: https://github.com/fb55/domutils/blob/master/LICENSE
+ :versions: []
+ :when: 2017-04-07 16:19:21.159356000 Z
+- - :approve
+ - entities
+ - :who: Mike Greiling
+ :why: https://github.com/fb55/entities/blob/master/LICENSE
+ :versions: []
+ :when: 2017-04-07 16:19:23.900571000 Z
+- - :approve
+ - ansi-html
+ - :who: Mike Greiling
+ :why: https://github.com/Tjatse/ansi-html/blob/master/LICENSE
+ :versions: []
+ :when: 2017-04-10 05:42:12.898178000 Z
+- - :approve
+ - map-stream
+ - :who: Mike Greiling
+ :why: https://github.com/dominictarr/map-stream/blob/master/LICENCE
+ :versions: []
+ :when: 2017-04-10 06:27:52.269085000 Z
+- - :approve
+ - pause-stream
+ - :who: Mike Greiling
+ :why: https://github.com/dominictarr/pause-stream/blob/master/LICENSE
+ :versions: []
+ :when: 2017-04-10 06:28:39.825894000 Z
+- - :approve
+ - undefsafe
+ - :who: Mike Greiling
+ :why: https://github.com/remy/undefsafe/blob/master/LICENSE
+ :versions: []
+ :when: 2017-04-10 06:30:00.002555000 Z
diff --git a/config/environments/test.rb b/config/environments/test.rb
index a25c5016a3b..c3b788c038e 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -8,7 +8,12 @@ Rails.application.configure do
# test suite. You never need to work with it otherwise. Remember that
# your test database is "scratch space" for the test suite and is wiped
# and recreated between test runs. Don't rely on the data there!
- config.cache_classes = false
+
+ # Enabling caching of classes slows start-up time because all controllers
+ # are loaded at initalization, but it reduces memory and load because files
+ # are not reloaded with every request. For example, caching is not necessary
+ # for loading database migrations but useful for handling Knapsack specs.
+ config.cache_classes = ENV['CACHE_CLASSES'] == 'true'
# Configure static asset server for tests with Cache-Control for performance
config.assets.digest = false
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 3c70f35b9d0..06c9f734c2a 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -579,9 +579,9 @@ test:
storages:
default:
path: tmp/tests/repositories/
- gitaly_address: unix:<%= Rails.root.join('tmp/sockets/private/gitaly.socket') %>
+ gitaly_address: unix:tmp/tests/gitaly/gitaly.socket
gitaly:
- enabled: false
+ enabled: true
backup:
path: tmp/tests/backups
gitlab_shell:
diff --git a/config/routes/project.rb b/config/routes/project.rb
index f5009186344..fa92202c1ea 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -42,29 +42,6 @@ constraints(ProjectUrlConstrainer.new) do
resources :domains, only: [:show, :new, :create, :destroy], controller: 'pages_domains', constraints: { id: /[^\/]+/ }
end
- resources :compare, only: [:index, :create] do
- collection do
- get :diff_for_path
- end
- end
-
- get '/compare/:from...:to', to: 'compare#show', as: 'compare', constraints: { from: /.+/, to: /.+/ }
-
- # Don't use format parameter as file extension (old 3.0.x behavior)
- # See http://guides.rubyonrails.org/routing.html#route-globbing-and-wildcard-segments
- scope format: false do
- resources :network, only: [:show], constraints: { id: Gitlab::Regex.git_reference_regex }
-
- resources :graphs, only: [:show], constraints: { id: Gitlab::Regex.git_reference_regex } do
- member do
- get :charts
- get :commits
- get :ci
- get :languages
- end
- end
- end
-
resources :snippets, concerns: :awardable, constraints: { id: /\d+/ } do
member do
get 'raw'
@@ -128,15 +105,6 @@ constraints(ProjectUrlConstrainer.new) do
end
end
- resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
- delete :merged_branches, controller: 'branches', action: :destroy_all_merged
- resources :tags, only: [:index, :show, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } do
- resource :release, only: [:edit, :update]
- end
-
- resources :protected_branches, only: [:index, :show, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
- resources :protected_tags, only: [:index, :show, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
-
resources :variables, only: [:index, :show, :update, :create, :destroy]
resources :triggers, only: [:index, :create, :edit, :update, :destroy] do
member do
diff --git a/config/routes/repository.rb b/config/routes/repository.rb
index f8966c5ae75..5cf37a06e97 100644
--- a/config/routes/repository.rb
+++ b/config/routes/repository.rb
@@ -1,4 +1,4 @@
-# All routing related to repositoty browsing
+# All routing related to repository browsing
resource :repository, only: [:create] do
member do
@@ -6,83 +6,84 @@ resource :repository, only: [:create] do
end
end
-resources :refs, only: [] do
- collection do
- get 'switch'
+# Don't use format parameter as file extension (old 3.0.x behavior)
+# See http://guides.rubyonrails.org/routing.html#route-globbing-and-wildcard-segments
+scope format: false do
+ get '/compare/:from...:to', to: 'compare#show', as: 'compare', constraints: { from: /.+/, to: /.+/ }
+
+ resources :compare, only: [:index, :create] do
+ collection do
+ get :diff_for_path
+ end
end
- member do
- # tree viewer logs
- get 'logs_tree', constraints: { id: Gitlab::Regex.git_reference_regex }
- # Directories with leading dots erroneously get rejected if git
- # ref regex used in constraints. Regex verification now done in controller.
- get 'logs_tree/*path' => 'refs#logs_tree', as: :logs_file, constraints: {
- id: /.*/,
- path: /.*/
- }
+ resources :refs, only: [] do
+ collection do
+ get 'switch'
+ end
+
+ member do
+ # tree viewer logs
+ get 'logs_tree', constraints: { id: Gitlab::Regex.git_reference_regex }
+ # Directories with leading dots erroneously get rejected if git
+ # ref regex used in constraints. Regex verification now done in controller.
+ get 'logs_tree/*path', action: :logs_tree, as: :logs_file, format: false, constraints: {
+ id: /.*/,
+ path: /.*/
+ }
+ end
end
-end
-get '/new/*id', to: 'blob#new', constraints: { id: /.+/ }, as: 'new_blob'
-post '/create/*id', to: 'blob#create', constraints: { id: /.+/ }, as: 'create_blob'
-get '/edit/*id', to: 'blob#edit', constraints: { id: /.+/ }, as: 'edit_blob'
-put '/update/*id', to: 'blob#update', constraints: { id: /.+/ }, as: 'update_blob'
-post '/preview/*id', to: 'blob#preview', constraints: { id: /.+/ }, as: 'preview_blob'
+ scope constraints: { id: Gitlab::Regex.git_reference_regex } do
+ resources :network, only: [:show]
-scope('/blob/*id', as: :blob, controller: :blob, constraints: { id: /.+/, format: false }) do
- get :diff
- get '/', action: :show
- delete '/', action: :destroy
- post '/', action: :create
- put '/', action: :update
-end
+ resources :graphs, only: [:show] do
+ member do
+ get :charts
+ get :commits
+ get :ci
+ get :languages
+ end
+ end
-get(
- '/raw/*id',
- to: 'raw#show',
- constraints: { id: /.+/, format: /(html|js)/ },
- as: :raw
-)
+ resources :branches, only: [:index, :new, :create, :destroy]
+ delete :merged_branches, controller: 'branches', action: :destroy_all_merged
+ resources :tags, only: [:index, :show, :new, :create, :destroy] do
+ resource :release, only: [:edit, :update]
+ end
-get(
- '/tree/*id',
- to: 'tree#show',
- constraints: { id: /.+/, format: /(html|js)/ },
- as: :tree
-)
+ resources :protected_branches, only: [:index, :show, :create, :update, :destroy]
+ resources :protected_tags, only: [:index, :show, :create, :update, :destroy]
+ end
+
+ scope constraints: { id: /.+/ } do
+ scope controller: :blob do
+ get '/new/*id', action: :new, as: :new_blob
+ post '/create/*id', action: :create, as: :create_blob
+ get '/edit/*id', action: :edit, as: :edit_blob
+ put '/update/*id', action: :update, as: :update_blob
+ post '/preview/*id', action: :preview, as: :preview_blob
-get(
- '/find_file/*id',
- to: 'find_file#show',
- constraints: { id: /.+/, format: /html/ },
- as: :find_file
-)
+ scope path: '/blob/*id', as: :blob do
+ get :diff
+ get '/', action: :show
+ delete '/', action: :destroy
+ post '/', action: :create
+ put '/', action: :update
+ end
+ end
-get(
- '/files/*id',
- to: 'find_file#list',
- constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ },
- as: :files
-)
+ get '/tree/*id', to: 'tree#show', as: :tree
+ get '/raw/*id', to: 'raw#show', as: :raw
+ get '/blame/*id', to: 'blame#show', as: :blame
+ get '/commits/*id', to: 'commits#show', as: :commits
-post(
- '/create_dir/*id',
- to: 'tree#create_dir',
- constraints: { id: /.+/ },
- as: 'create_dir'
-)
+ post '/create_dir/*id', to: 'tree#create_dir', as: :create_dir
-get(
- '/blame/*id',
- to: 'blame#show',
- constraints: { id: /.+/, format: /(html|js)/ },
- as: :blame
-)
+ scope controller: :find_file do
+ get '/find_file/*id', action: :show, as: :find_file
-# File/dir history
-get(
- '/commits/*id',
- to: 'commits#show',
- constraints: { id: /.+/, format: false },
- as: :commits
-)
+ get '/files/*id', action: :list, as: :files
+ end
+ end
+end
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 987344f4eaa..64a04dc342e 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -6,10 +6,12 @@ var webpack = require('webpack');
var StatsPlugin = require('stats-webpack-plugin');
var CompressionPlugin = require('compression-webpack-plugin');
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
+var WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
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_HOST = process.env.DEV_SERVER_HOST || 'localhost';
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;
@@ -49,6 +51,7 @@ var config = {
users: './users/users_bundle.js',
vue_pipelines: './vue_pipelines_index/index.js',
issue_show: './issue_show/index.js',
+ group: './group.js',
},
output: {
@@ -64,13 +67,18 @@ var config = {
{
test: /\.js$/,
exclude: /(node_modules|vendor\/assets)/,
- loader: 'babel-loader'
+ loader: 'babel-loader',
+ },
+ {
+ test: /\.vue$/,
+ loader: 'vue-loader',
},
{
test: /\.svg$/,
- use: 'raw-loader'
- }, {
- test: /\.(worker.js|pdf)$/,
+ loader: 'raw-loader',
+ },
+ {
+ test: /\.(worker\.js|pdf)$/,
exclude: /node_modules/,
loader: 'file-loader',
},
@@ -117,6 +125,7 @@ var config = {
'notebook_viewer',
'pdf_viewer',
'vue_pipelines',
+ 'issue_show',
],
minChunks: function(module, count) {
return module.resource && (/vue_shared/).test(module.resource);
@@ -175,12 +184,17 @@ if (IS_PRODUCTION) {
if (IS_DEV_SERVER) {
config.devtool = 'cheap-module-eval-source-map';
config.devServer = {
+ host: DEV_SERVER_HOST,
port: DEV_SERVER_PORT,
headers: { 'Access-Control-Allow-Origin': '*' },
stats: 'errors-only',
inline: DEV_SERVER_LIVERELOAD
};
- config.output.publicPath = '//localhost:' + DEV_SERVER_PORT + config.output.publicPath;
+ config.output.publicPath = '//' + DEV_SERVER_HOST + ':' + DEV_SERVER_PORT + config.output.publicPath;
+ config.plugins.push(
+ // watch node_modules for changes if we encounter a missing module compile error
+ new WatchMissingNodeModulesPlugin(path.join(ROOT_PATH, 'node_modules'))
+ );
}
if (WEBPACK_REPORT) {
diff --git a/db/fixtures/development/17_cycle_analytics.rb b/db/fixtures/development/17_cycle_analytics.rb
index 4bc735916c1..0d7eb1a7c93 100644
--- a/db/fixtures/development/17_cycle_analytics.rb
+++ b/db/fixtures/development/17_cycle_analytics.rb
@@ -223,7 +223,9 @@ class Gitlab::Seeder::CycleAnalytics
end
Gitlab::Seeder.quiet do
- if ENV['SEED_CYCLE_ANALYTICS']
+ flag = 'SEED_CYCLE_ANALYTICS'
+
+ if ENV[flag]
Project.all.each do |project|
seeder = Gitlab::Seeder::CycleAnalytics.new(project)
seeder.seed!
@@ -235,6 +237,6 @@ Gitlab::Seeder.quiet do
seeder = Gitlab::Seeder::CycleAnalytics.new(Project.order(:id).first, perf: true)
seeder.seed_metrics!
else
- puts "Not running the cycle analytics seed file. Use the `SEED_CYCLE_ANALYTICS` environment variable to enable it."
+ puts "Skipped. Use the `#{flag}` environment variable to enable."
end
end
diff --git a/db/fixtures/development/20_nested_groups.rb b/db/fixtures/development/20_nested_groups.rb
index d8dddc3fee9..2bc78e120a5 100644
--- a/db/fixtures/development/20_nested_groups.rb
+++ b/db/fixtures/development/20_nested_groups.rb
@@ -27,43 +27,49 @@ end
Sidekiq::Testing.inline! do
Gitlab::Seeder.quiet do
- project_urls = [
- 'https://android.googlesource.com/platform/hardware/broadcom/libbt.git',
- 'https://android.googlesource.com/platform/hardware/broadcom/wlan.git',
- 'https://android.googlesource.com/platform/hardware/bsp/bootloader/intel/edison-u-boot.git',
- 'https://android.googlesource.com/platform/hardware/bsp/broadcom.git',
- 'https://android.googlesource.com/platform/hardware/bsp/freescale.git',
- 'https://android.googlesource.com/platform/hardware/bsp/imagination.git',
- 'https://android.googlesource.com/platform/hardware/bsp/intel.git',
- 'https://android.googlesource.com/platform/hardware/bsp/kernel/common/v4.1.git',
- 'https://android.googlesource.com/platform/hardware/bsp/kernel/common/v4.4.git'
- ]
+ flag = 'SEED_NESTED_GROUPS'
- user = User.admins.first
+ if ENV[flag]
+ project_urls = [
+ 'https://android.googlesource.com/platform/hardware/broadcom/libbt.git',
+ 'https://android.googlesource.com/platform/hardware/broadcom/wlan.git',
+ 'https://android.googlesource.com/platform/hardware/bsp/bootloader/intel/edison-u-boot.git',
+ 'https://android.googlesource.com/platform/hardware/bsp/broadcom.git',
+ 'https://android.googlesource.com/platform/hardware/bsp/freescale.git',
+ 'https://android.googlesource.com/platform/hardware/bsp/imagination.git',
+ 'https://android.googlesource.com/platform/hardware/bsp/intel.git',
+ 'https://android.googlesource.com/platform/hardware/bsp/kernel/common/v4.1.git',
+ 'https://android.googlesource.com/platform/hardware/bsp/kernel/common/v4.4.git'
+ ]
- project_urls.each_with_index do |url, i|
- full_path = url.sub('https://android.googlesource.com/', '')
- full_path = full_path.sub(/\.git\z/, '')
- full_path, _, project_path = full_path.rpartition('/')
- group = Group.find_by_full_path(full_path) || create_group_with_parents(user, full_path)
+ user = User.admins.first
- params = {
- import_url: url,
- namespace_id: group.id,
- path: project_path,
- name: project_path,
- description: FFaker::Lorem.sentence,
- visibility_level: Gitlab::VisibilityLevel.values.sample
- }
+ project_urls.each_with_index do |url, i|
+ full_path = url.sub('https://android.googlesource.com/', '')
+ full_path = full_path.sub(/\.git\z/, '')
+ full_path, _, project_path = full_path.rpartition('/')
+ group = Group.find_by_full_path(full_path) || create_group_with_parents(user, full_path)
- project = Projects::CreateService.new(user, params).execute
- project.send(:_run_after_commit_queue)
+ params = {
+ import_url: url,
+ namespace_id: group.id,
+ path: project_path,
+ name: project_path,
+ description: FFaker::Lorem.sentence,
+ visibility_level: Gitlab::VisibilityLevel.values.sample
+ }
- if project.valid?
- print '.'
- else
- print 'F'
+ project = Projects::CreateService.new(user, params).execute
+ project.send(:_run_after_commit_queue)
+
+ if project.valid?
+ print '.'
+ else
+ print 'F'
+ end
end
+ else
+ puts "Skipped. Use the `#{flag}` environment variable to enable."
end
end
end
diff --git a/db/migrate/20130218141258_convert_closed_to_state_in_issue.rb b/db/migrate/20130218141258_convert_closed_to_state_in_issue.rb
index 94c0a6845d5..67a0d3b53eb 100644
--- a/db/migrate/20130218141258_convert_closed_to_state_in_issue.rb
+++ b/db/migrate/20130218141258_convert_closed_to_state_in_issue.rb
@@ -1,6 +1,6 @@
# rubocop:disable all
class ConvertClosedToStateInIssue < ActiveRecord::Migration
- include Gitlab::Database
+ include Gitlab::Database::MigrationHelpers
def up
execute "UPDATE #{table_name} SET state = 'closed' WHERE closed = #{true_value}"
diff --git a/db/migrate/20130218141327_convert_closed_to_state_in_merge_request.rb b/db/migrate/20130218141327_convert_closed_to_state_in_merge_request.rb
index 64a9c761352..307fc6a023d 100644
--- a/db/migrate/20130218141327_convert_closed_to_state_in_merge_request.rb
+++ b/db/migrate/20130218141327_convert_closed_to_state_in_merge_request.rb
@@ -1,6 +1,6 @@
# rubocop:disable all
class ConvertClosedToStateInMergeRequest < ActiveRecord::Migration
- include Gitlab::Database
+ include Gitlab::Database::MigrationHelpers
def up
execute "UPDATE #{table_name} SET state = 'merged' WHERE closed = #{true_value} AND merged = #{true_value}"
diff --git a/db/migrate/20130218141344_convert_closed_to_state_in_milestone.rb b/db/migrate/20130218141344_convert_closed_to_state_in_milestone.rb
index 41508c2dc95..d12703cf3b2 100644
--- a/db/migrate/20130218141344_convert_closed_to_state_in_milestone.rb
+++ b/db/migrate/20130218141344_convert_closed_to_state_in_milestone.rb
@@ -1,6 +1,6 @@
# rubocop:disable all
class ConvertClosedToStateInMilestone < ActiveRecord::Migration
- include Gitlab::Database
+ include Gitlab::Database::MigrationHelpers
def up
execute "UPDATE #{table_name} SET state = 'closed' WHERE closed = #{true_value}"
diff --git a/db/migrate/20130315124931_user_color_scheme.rb b/db/migrate/20130315124931_user_color_scheme.rb
index 06e28a49d9d..09af928fde7 100644
--- a/db/migrate/20130315124931_user_color_scheme.rb
+++ b/db/migrate/20130315124931_user_color_scheme.rb
@@ -1,6 +1,6 @@
# rubocop:disable all
class UserColorScheme < ActiveRecord::Migration
- include Gitlab::Database
+ include Gitlab::Database::MigrationHelpers
def up
add_column :users, :color_scheme_id, :integer, null: false, default: 1
diff --git a/db/migrate/20131112220935_add_visibility_level_to_projects.rb b/db/migrate/20131112220935_add_visibility_level_to_projects.rb
index 5efc17b228e..86d73753adc 100644
--- a/db/migrate/20131112220935_add_visibility_level_to_projects.rb
+++ b/db/migrate/20131112220935_add_visibility_level_to_projects.rb
@@ -1,6 +1,6 @@
# rubocop:disable all
class AddVisibilityLevelToProjects < ActiveRecord::Migration
- include Gitlab::Database
+ include Gitlab::Database::MigrationHelpers
def self.up
add_column :projects, :visibility_level, :integer, :default => 0, :null => false
diff --git a/db/migrate/20140313092127_migrate_already_imported_projects.rb b/db/migrate/20140313092127_migrate_already_imported_projects.rb
index f2e91fe1b40..0afc26b8764 100644
--- a/db/migrate/20140313092127_migrate_already_imported_projects.rb
+++ b/db/migrate/20140313092127_migrate_already_imported_projects.rb
@@ -1,6 +1,6 @@
# rubocop:disable all
class MigrateAlreadyImportedProjects < ActiveRecord::Migration
- include Gitlab::Database
+ include Gitlab::Database::MigrationHelpers
def up
execute("UPDATE projects SET import_status = 'finished' WHERE imported = #{true_value}")
diff --git a/db/migrate/20141007100818_add_visibility_level_to_snippet.rb b/db/migrate/20141007100818_add_visibility_level_to_snippet.rb
index 688d8578478..0c14f75c154 100644
--- a/db/migrate/20141007100818_add_visibility_level_to_snippet.rb
+++ b/db/migrate/20141007100818_add_visibility_level_to_snippet.rb
@@ -1,6 +1,6 @@
# rubocop:disable all
class AddVisibilityLevelToSnippet < ActiveRecord::Migration
- include Gitlab::Database
+ include Gitlab::Database::MigrationHelpers
def up
add_column :snippets, :visibility_level, :integer, :default => 0, :null => false
diff --git a/db/migrate/20151209144329_migrate_ci_web_hooks.rb b/db/migrate/20151209144329_migrate_ci_web_hooks.rb
index cb1e556623a..62a6d334f04 100644
--- a/db/migrate/20151209144329_migrate_ci_web_hooks.rb
+++ b/db/migrate/20151209144329_migrate_ci_web_hooks.rb
@@ -1,6 +1,6 @@
# rubocop:disable all
class MigrateCiWebHooks < ActiveRecord::Migration
- include Gitlab::Database
+ include Gitlab::Database::MigrationHelpers
def up
execute(
diff --git a/db/migrate/20151209145909_migrate_ci_emails.rb b/db/migrate/20151209145909_migrate_ci_emails.rb
index 6b7a106814d..5de7b205fb1 100644
--- a/db/migrate/20151209145909_migrate_ci_emails.rb
+++ b/db/migrate/20151209145909_migrate_ci_emails.rb
@@ -1,6 +1,6 @@
# rubocop:disable all
class MigrateCiEmails < ActiveRecord::Migration
- include Gitlab::Database
+ include Gitlab::Database::MigrationHelpers
def up
# This inserts a new service: BuildsEmailService
diff --git a/db/migrate/20151210125232_migrate_ci_slack_service.rb b/db/migrate/20151210125232_migrate_ci_slack_service.rb
index 633d5148d97..fff130b7b10 100644
--- a/db/migrate/20151210125232_migrate_ci_slack_service.rb
+++ b/db/migrate/20151210125232_migrate_ci_slack_service.rb
@@ -1,6 +1,6 @@
# rubocop:disable all
class MigrateCiSlackService < ActiveRecord::Migration
- include Gitlab::Database
+ include Gitlab::Database::MigrationHelpers
def up
properties_query = 'SELECT properties FROM ci_services ' \
diff --git a/db/migrate/20151210125927_migrate_ci_hip_chat_service.rb b/db/migrate/20151210125927_migrate_ci_hip_chat_service.rb
index dae084ce180..824f6f84195 100644
--- a/db/migrate/20151210125927_migrate_ci_hip_chat_service.rb
+++ b/db/migrate/20151210125927_migrate_ci_hip_chat_service.rb
@@ -1,6 +1,6 @@
# rubocop:disable all
class MigrateCiHipChatService < ActiveRecord::Migration
- include Gitlab::Database
+ include Gitlab::Database::MigrationHelpers
def up
# From properties strip `hipchat_` key
diff --git a/db/migrate/20170407135259_add_foreigh_key_trigger_requests_trigger.rb b/db/migrate/20170407135259_add_foreigh_key_trigger_requests_trigger.rb
new file mode 100644
index 00000000000..81761c65a9f
--- /dev/null
+++ b/db/migrate/20170407135259_add_foreigh_key_trigger_requests_trigger.rb
@@ -0,0 +1,15 @@
+class AddForeighKeyTriggerRequestsTrigger < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_foreign_key(:ci_trigger_requests, :ci_triggers, column: :trigger_id)
+ end
+
+ def down
+ remove_foreign_key(:ci_trigger_requests, column: :trigger_id)
+ end
+end
diff --git a/db/post_migrate/20170301205640_migrate_build_events_to_pipeline_events.rb b/db/post_migrate/20170301205640_migrate_build_events_to_pipeline_events.rb
index 2dd14ee5a78..04bf89c9687 100644
--- a/db/post_migrate/20170301205640_migrate_build_events_to_pipeline_events.rb
+++ b/db/post_migrate/20170301205640_migrate_build_events_to_pipeline_events.rb
@@ -1,6 +1,5 @@
class MigrateBuildEventsToPipelineEvents < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
- include Gitlab::Database
DOWNTIME = false
diff --git a/db/post_migrate/20170406142253_migrate_user_project_view.rb b/db/post_migrate/20170406142253_migrate_user_project_view.rb
new file mode 100644
index 00000000000..22f0f2ac200
--- /dev/null
+++ b/db/post_migrate/20170406142253_migrate_user_project_view.rb
@@ -0,0 +1,19 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class MigrateUserProjectView < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def up
+ update_column_in_batches(:users, :project_view, 2) do |table, query|
+ query.where(table[:project_view].eq(0))
+ end
+ end
+
+ def down
+ # Nothing can be done to restore old values
+ end
+end
diff --git a/db/post_migrate/20170408033905_remove_old_cache_directories.rb b/db/post_migrate/20170408033905_remove_old_cache_directories.rb
new file mode 100644
index 00000000000..b23b52896b9
--- /dev/null
+++ b/db/post_migrate/20170408033905_remove_old_cache_directories.rb
@@ -0,0 +1,23 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+# Remove all files from old custom carrierwave's cache directories.
+# See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9466
+
+class RemoveOldCacheDirectories < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ # FileUploader cache.
+ FileUtils.rm_rf(Dir[Rails.root.join('public', 'uploads', 'tmp', '*')])
+ end
+
+ def down
+ # Old cache is not supposed to be recoverable.
+ # So the down method is empty.
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index d400c823387..5689f7331dc 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,8 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20170407140450) do
+ActiveRecord::Schema.define(version: 20170408033905) do
+
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
enable_extension "pg_trgm"
@@ -1356,6 +1357,7 @@ ActiveRecord::Schema.define(version: 20170407140450) do
add_foreign_key "chat_teams", "namespaces", on_delete: :cascade
add_foreign_key "ci_builds", "ci_pipelines", column: "auto_canceled_by_id", name: "fk_a2141b1522", on_delete: :nullify
add_foreign_key "ci_pipelines", "ci_pipelines", column: "auto_canceled_by_id", name: "fk_262d4c2d19", on_delete: :nullify
+ add_foreign_key "ci_trigger_requests", "ci_triggers", column: "trigger_id", name: "fk_b8ec8b7245", on_delete: :cascade
add_foreign_key "ci_trigger_schedules", "ci_triggers", column: "trigger_id", name: "fk_90a406cc94", on_delete: :cascade
add_foreign_key "ci_triggers", "users", column: "owner_id", name: "fk_e8e10d1964", on_delete: :cascade
add_foreign_key "container_repositories", "projects"
diff --git a/doc/README.md b/doc/README.md
index b0b3d2156a7..7703c64e152 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -6,10 +6,6 @@ All technical content published by GitLab lives in the documentation, including:
- [User docs](#user-documentation): general documentation dedicated to regular users of GitLab
- [Admin docs](#administrator-documentation): general documentation dedicated to administrators of GitLab instances
- [Contributor docs](#contributor-documentation): general documentation on how to develop and contribute to GitLab
-- [Topics](topics/index.md): pages organized per topic, gathering all the
- resources already published by GitLab related to a specific subject, including
- general docs, [technical articles](development/writing_documentation.md#technical-articles),
- blog posts and video tutorials.
- [GitLab University](university/README.md): guides to learn Git and GitLab
through courses and videos.
@@ -62,6 +58,7 @@ All technical content published by GitLab lives in the documentation, including:
- [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.
- [Monitoring uptime](user/admin_area/monitoring/health_check.md) Check the server status using the health check endpoint.
- [Operations](administration/operations.md) Keeping GitLab up and running.
+- [Polling](administration/polling.md) Configure how often the GitLab UI polls for updates
- [Raketasks](raketasks/README.md) Backups, maintenance, automatic webhook setup and the importing of projects.
- [Reply by email](administration/reply_by_email.md) Allow users to comment on issues and merge requests by replying to notification emails.
- [Repository checks](administration/repository_checks.md) Periodic Git repository checks.
diff --git a/doc/administration/polling.md b/doc/administration/polling.md
new file mode 100644
index 00000000000..35aaa20df2c
--- /dev/null
+++ b/doc/administration/polling.md
@@ -0,0 +1,24 @@
+# Polling configuration
+
+The GitLab UI polls for updates for different resources (issue notes, issue
+titles, pipeline statuses, etc.) on a schedule appropriate to the resource.
+
+In "Application settings -> Real-time features" you can configure "Polling
+interval multiplier". This multiplier is applied to all resources at once,
+and decimal values are supported. For the sake of the examples below, we will
+say that issue notes poll every 2 seconds, and issue titles poll every 5
+seconds; these are _not_ the actual values.
+
+- 1 is the default, and recommended for most installations. (Issue notes poll
+every 2 seconds, and issue titles poll every 5 seconds.)
+- 0 will disable UI polling completely. (On the next poll, clients will stop
+polling for updates.)
+- A value greater than 1 will slow polling down. If you see issues with
+database load from lots of clients polling for updates, increasing the
+multiplier from 1 can be a good compromise, rather than disabling polling
+completely. (For example: If this is set to 2, then issue notes poll every 4
+seconds, and issue titles poll every 10 seconds.)
+- A value between 0 and 1 will make the UI poll more frequently (so updates
+will show in other sessions faster), but is **not recommended**. 1 should be
+fast enough. (For example, if this is set to 0.5, then issue notes poll every
+1 second, and issue titles poll every 2.5 seconds.)
diff --git a/doc/api/README.md b/doc/api/README.md
index e627b6f2ee8..d444ce94573 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -303,6 +303,17 @@ Additional pagination headers are also sent back.
| `X-Next-Page` | The index of the next page |
| `X-Prev-Page` | The index of the previous page |
+## Namespaced path encoding
+
+If using namespaced API calls, make sure that the `NAMESPACE/PROJECT_NAME` is
+URL-encoded.
+
+For example, `/` is represented by `%2F`:
+
+```
+/api/v4/projects/diaspora%2Fdiaspora
+```
+
## `id` vs `iid`
When you work with the API, you may notice two similar fields in API entities:
@@ -398,7 +409,6 @@ Content-Type: application/json
}
```
-
## Clients
There are many unofficial GitLab API Clients for most of the popular
diff --git a/doc/api/access_requests.md b/doc/api/access_requests.md
index 96b8d654c58..21de7d18632 100644
--- a/doc/api/access_requests.md
+++ b/doc/api/access_requests.md
@@ -25,7 +25,7 @@ GET /projects/:id/access_requests
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The group/project ID or path |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/access_requests
@@ -66,7 +66,7 @@ POST /projects/:id/access_requests
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The group/project ID or path |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/access_requests
@@ -97,7 +97,7 @@ PUT /projects/:id/access_requests/:user_id/approve
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The group/project ID or path |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `user_id` | integer | yes | The user ID of the access requester |
| `access_level` | integer | no | A valid access level (defaults: `30`, developer access level) |
@@ -130,7 +130,7 @@ DELETE /projects/:id/access_requests/:user_id
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The group/project ID or path |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `user_id` | integer | yes | The user ID of the access requester |
```bash
diff --git a/doc/api/award_emoji.md b/doc/api/award_emoji.md
index f57928d3c93..5f3adcc397a 100644
--- a/doc/api/award_emoji.md
+++ b/doc/api/award_emoji.md
@@ -23,7 +23,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `awardable_id` | integer | yes | The ID (`iid` for merge requests/issues, `id` for snippets) of an awardable |
```bash
@@ -83,7 +83,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `awardable_id` | integer | yes | The ID (`iid` for merge requests/issues, `id` for snippets) of an awardable |
| `award_id` | integer | yes | The ID of the award emoji |
@@ -126,7 +126,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `awardable_id` | integer | yes | The ID (`iid` for merge requests/issues, `id` for snippets) of an awardable |
| `name` | string | yes | The name of the emoji, without colons |
@@ -152,7 +152,7 @@ Example Response:
"updated_at": "2016-06-17T17:47:29.266Z",
"awardable_id": 80,
"awardable_type": "Issue"
-}
+}
```
### Delete an award emoji
@@ -170,7 +170,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `issue_iid` | integer | yes | The internal ID of an issue |
| `award_id` | integer | yes | The ID of a award_emoji |
@@ -195,7 +195,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `issue_iid` | integer | yes | The internal ID of an issue |
| `note_id` | integer | yes | The ID of an note |
@@ -237,7 +237,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `issue_iid` | integer | yes | The internal ID of an issue |
| `note_id` | integer | yes | The ID of a note |
| `award_id` | integer | yes | The ID of the award emoji |
@@ -277,7 +277,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `issue_iid` | integer | yes | The internal ID of an issue |
| `note_id` | integer | yes | The ID of a note |
| `name` | string | yes | The name of the emoji, without colons |
@@ -320,7 +320,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `issue_iid` | integer | yes | The internal ID of an issue |
| `note_id` | integer | yes | The ID of a note |
| `award_id` | integer | yes | The ID of a award_emoji |
diff --git a/doc/api/boards.md b/doc/api/boards.md
index b2106463639..17d2be0ee16 100644
--- a/doc/api/boards.md
+++ b/doc/api/boards.md
@@ -15,7 +15,7 @@ GET /projects/:id/boards
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/boards
@@ -71,7 +71,7 @@ GET /projects/:id/boards/:board_id/lists
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `board_id` | integer | yes | The ID of a board |
```bash
@@ -122,7 +122,7 @@ GET /projects/:id/boards/:board_id/lists/:list_id
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `board_id` | integer | yes | The ID of a board |
| `list_id`| integer | yes | The ID of a board's list |
@@ -154,7 +154,7 @@ POST /projects/:id/boards/:board_id/lists
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `board_id` | integer | yes | The ID of a board |
| `label_id` | integer | yes | The ID of a label |
@@ -186,7 +186,7 @@ PUT /projects/:id/boards/:board_id/lists/:list_id
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `board_id` | integer | yes | The ID of a board |
| `list_id` | integer | yes | The ID of a board's list |
| `position` | integer | yes | The position of the list |
@@ -219,7 +219,7 @@ DELETE /projects/:id/boards/:board_id/lists/:list_id
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `board_id` | integer | yes | The ID of a board |
| `list_id` | integer | yes | The ID of a board's list |
diff --git a/doc/api/branches.md b/doc/api/branches.md
index 815aabda8e3..5717215deb6 100644
--- a/doc/api/branches.md
+++ b/doc/api/branches.md
@@ -12,7 +12,7 @@ GET /projects/:id/repository/branches
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/repository/branches
@@ -59,7 +59,7 @@ GET /projects/:id/repository/branches/:branch
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `branch` | string | yes | The name of the branch |
```bash
@@ -109,7 +109,7 @@ curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `branch` | string | yes | The name of the branch |
| `developers_can_push` | boolean | no | Flag if developers can push to the branch |
| `developers_can_merge` | boolean | no | Flag if developers can merge to the branch |
@@ -157,7 +157,7 @@ curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `branch` | string | yes | The name of the branch |
Example response:
@@ -195,7 +195,7 @@ POST /projects/:id/repository/branches
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `branch` | string | yes | The name of the branch |
| `ref` | string | yes | The branch name or commit SHA to create branch from |
@@ -238,7 +238,7 @@ DELETE /projects/:id/repository/branches/:branch
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `branch` | string | yes | The name of the branch |
In case of an error, an explaining message is provided.
@@ -257,7 +257,7 @@ DELETE /projects/:id/repository/merged_branches
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
```bash
diff --git a/doc/api/build_variables.md b/doc/api/build_variables.md
index 1c26e9b33ab..9218902e84a 100644
--- a/doc/api/build_variables.md
+++ b/doc/api/build_variables.md
@@ -10,7 +10,7 @@ GET /projects/:id/variables
| Attribute | Type | required | Description |
|-----------|---------|----------|---------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID of a project or [urlencoded NAMESPACE/PROJECT_NAME of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
```
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/variables"
@@ -39,7 +39,7 @@ GET /projects/:id/variables/:key
| Attribute | Type | required | Description |
|-----------|---------|----------|-----------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID of a project or [urlencoded NAMESPACE/PROJECT_NAME of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `key` | string | yes | The `key` of a variable |
```
@@ -63,7 +63,7 @@ POST /projects/:id/variables
| Attribute | Type | required | Description |
|-----------|---------|----------|-----------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID of a project or [urlencoded NAMESPACE/PROJECT_NAME of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `key` | string | yes | The `key` of a variable; must have no more than 255 characters; only `A-Z`, `a-z`, `0-9`, and `_` are allowed |
| `value` | string | yes | The `value` of a variable |
@@ -88,7 +88,7 @@ PUT /projects/:id/variables/:key
| Attribute | Type | required | Description |
|-----------|---------|----------|-------------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID of a project or [urlencoded NAMESPACE/PROJECT_NAME of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `key` | string | yes | The `key` of a variable |
| `value` | string | yes | The `value` of a variable |
@@ -113,7 +113,7 @@ DELETE /projects/:id/variables/:key
| Attribute | Type | required | Description |
|-----------|---------|----------|-------------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID of a project or [urlencoded NAMESPACE/PROJECT_NAME of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `key` | string | yes | The `key` of a variable |
```
diff --git a/doc/api/commits.md b/doc/api/commits.md
index 24c402346b1..9cb58dd3ae9 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -10,7 +10,7 @@ GET /projects/:id/repository/commits
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
| `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 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 |
@@ -68,7 +68,7 @@ POST /projects/:id/repository/commits
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `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. |
@@ -155,7 +155,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
| `sha` | string | yes | The commit hash or name of a repository branch or tag |
```bash
@@ -203,7 +203,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
| `sha` | string | yes | The commit hash |
| `branch` | string | yes | The name of the branch |
@@ -245,7 +245,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
| `sha` | string | yes | The commit hash or name of a repository branch or tag |
```bash
@@ -281,7 +281,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
| `sha` | string | yes | The commit hash or name of a repository branch or tag |
```bash
@@ -330,7 +330,7 @@ POST /projects/:id/repository/commits/:sha/comments
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
| `sha` | string | yes | The commit SHA or name of a repository branch or tag |
| `note` | string | yes | The text of the comment |
| `path` | string | no | The file path relative to the repository |
@@ -375,7 +375,7 @@ GET /projects/:id/repository/commits/:sha/statuses
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
| `sha` | string | yes | The commit SHA
| `ref` | string | no | The name of a repository branch or tag or, if not given, the default branch
| `stage` | string | no | Filter by [build stage](../ci/yaml/README.md#stages), e.g., `test`
@@ -449,7 +449,7 @@ POST /projects/:id/statuses/:sha
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
| `sha` | string | yes | The commit SHA
| `state` | string | yes | The state of the status. Can be one of the following: `pending`, `running`, `success`, `failed`, `canceled`
| `ref` | string | no | The `ref` (branch or tag) to which the status refers
diff --git a/doc/api/deploy_keys.md b/doc/api/deploy_keys.md
index f051f55ac3e..c3fe7f84ef2 100644
--- a/doc/api/deploy_keys.md
+++ b/doc/api/deploy_keys.md
@@ -43,7 +43,7 @@ GET /projects/:id/deploy_keys
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of the project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/deploy_keys"
@@ -82,7 +82,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of the project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `key_id` | integer | yes | The ID of the deploy key |
```bash
@@ -114,7 +114,7 @@ POST /projects/:id/deploy_keys
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of the project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `title` | string | yes | New deploy key's title |
| `key` | string | yes | New deploy key |
| `can_push` | boolean | no | Can deploy key push to the project's repository |
@@ -145,7 +145,7 @@ DELETE /projects/:id/deploy_keys/:key_id
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of the project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `key_id` | integer | yes | The ID of the deploy key |
```bash
@@ -162,7 +162,7 @@ curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitla
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of the project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `key_id` | integer | yes | The ID of the deploy key |
Example response:
diff --git a/doc/api/deployments.md b/doc/api/deployments.md
index 76e18c8a9bd..0273c819614 100644
--- a/doc/api/deployments.md
+++ b/doc/api/deployments.md
@@ -10,7 +10,7 @@ GET /projects/:id/deployments
| Attribute | Type | Required | Description |
|-----------|---------|----------|---------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/deployments"
@@ -147,7 +147,7 @@ GET /projects/:id/deployments/:deployment_id
| Attribute | Type | Required | Description |
|-----------|---------|----------|---------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `deployment_id` | integer | yes | The ID of the deployment |
```bash
diff --git a/doc/api/enviroments.md b/doc/api/enviroments.md
index 3f0a8d989f9..49930f01945 100644
--- a/doc/api/enviroments.md
+++ b/doc/api/enviroments.md
@@ -10,7 +10,7 @@ GET /projects/:id/environments
| Attribute | Type | Required | Description |
| --------- | ------- | -------- | --------------------- |
-| `id` | integer | yes | The ID of the project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/environments
@@ -41,7 +41,7 @@ POST /projects/:id/environment
| Attribute | Type | Required | Description |
| ------------- | ------- | -------- | ---------------------------- |
-| `id` | integer | yes | The ID of the project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `name` | string | yes | The name of the environment |
| `external_url` | string | no | Place to link to for this environment |
@@ -72,7 +72,7 @@ PUT /projects/:id/environments/:environments_id
| Attribute | Type | Required | Description |
| --------------- | ------- | --------------------------------- | ------------------------------- |
-| `id` | integer | yes | The ID of the project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `environment_id` | integer | yes | The ID of the environment | The ID of the environment |
| `name` | string | no | The new name of the environment |
| `external_url` | string | no | The new external_url |
@@ -102,7 +102,7 @@ DELETE /projects/:id/environments/:environment_id
| Attribute | Type | Required | Description |
| --------- | ------- | -------- | --------------------- |
-| `id` | integer | yes | The ID of the project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `environment_id` | integer | yes | The ID of the environment |
```bash
@@ -119,7 +119,7 @@ POST /projects/:id/environments/:environment_id/stop
| Attribute | Type | Required | Description |
| --------- | ------- | -------- | --------------------- |
-| `id` | integer | yes | The ID of the project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `environment_id` | integer | yes | The ID of the environment |
```bash
diff --git a/doc/api/groups.md b/doc/api/groups.md
index dfc6b80bfd9..bc61bfec9b9 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -53,7 +53,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or path of a group |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `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` |
@@ -119,7 +119,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or path of a group |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/4
@@ -299,7 +299,7 @@ POST /groups/:id/projects/:project_id
Parameters:
-- `id` (required) - The ID or path of a group
+- `id` (required) - The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user
- `project_id` (required) - The ID or path of a project
## Update group
diff --git a/doc/api/issues.md b/doc/api/issues.md
index 54c099d4bf8..5f01fcdd396 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -111,7 +111,7 @@ GET /groups/:id/issues?search=issue+title+or+description
| Attribute | Type | Required | Description |
|-------------|----------------|----------|-----------------------------------------------------------------------------------------------------------------------------|
-| `id` | integer | yes | The ID of a group |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `state` | string | no | Return all issues or just those that are `opened` or `closed` |
| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels |
| `iids` | Array[integer] | no | Return only the issues having the given `iid` |
@@ -193,7 +193,7 @@ GET /projects/:id/issues?search=issue+title+or+description
| Attribute | Type | Required | Description |
|-------------|----------------|----------|-----------------------------------------------------------------------------------------------------------------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `iids` | Array[integer] | no | Return only the milestone 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 must have all labels to be returned. `No+Label` lists all issues with no labels |
@@ -266,7 +266,7 @@ GET /projects/:id/issues/:issue_iid
| Attribute | Type | Required | Description |
|-------------|---------|----------|--------------------------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash
@@ -331,7 +331,7 @@ POST /projects/:id/issues
| Attribute | Type | Required | Description |
|-------------------------------------------|---------|----------|--------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `title` | string | yes | The title of an issue |
| `description` | string | no | The description of an issue |
| `confidential` | boolean | no | Set an issue to be confidential. Default is `false`. |
@@ -391,7 +391,7 @@ PUT /projects/:id/issues/:issue_iid
| Attribute | Type | Required | Description |
|----------------|---------|----------|------------------------------------------------------------------------------------------------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `issue_iid` | integer | yes | The internal ID of a project's issue |
| `title` | string | no | The title of an issue |
| `description` | string | no | The description of an issue |
@@ -450,7 +450,7 @@ DELETE /projects/:id/issues/:issue_iid
| Attribute | Type | Required | Description |
|-------------|---------|----------|--------------------------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash
@@ -472,7 +472,7 @@ POST /projects/:id/issues/:issue_iid/move
| Attribute | Type | Required | Description |
|-----------------|---------|----------|--------------------------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `issue_iid` | integer | yes | The internal ID of a project's issue |
| `to_project_id` | integer | yes | The ID of the new project |
@@ -528,7 +528,7 @@ POST /projects/:id/issues/:issue_iid/subscribe
| Attribute | Type | Required | Description |
|-------------|---------|----------|--------------------------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash
@@ -583,7 +583,7 @@ POST /projects/:id/issues/:issue_iid/unsubscribe
| Attribute | Type | Required | Description |
|-------------|---------|----------|--------------------------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash
@@ -602,7 +602,7 @@ POST /projects/:id/issues/:issue_iid/todo
| Attribute | Type | Required | Description |
|-------------|---------|----------|--------------------------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash
@@ -693,7 +693,7 @@ POST /projects/:id/issues/:issue_iid/time_estimate
| Attribute | Type | Required | Description |
|-------------|---------|----------|------------------------------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `issue_iid` | integer | yes | The internal ID of a project's issue |
| `duration` | string | yes | The duration in human format. e.g: 3h30m |
@@ -722,7 +722,7 @@ POST /projects/:id/issues/:issue_iid/reset_time_estimate
| Attribute | Type | Required | Description |
|-------------|---------|----------|--------------------------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash
@@ -750,7 +750,7 @@ POST /projects/:id/issues/:issue_iid/add_spent_time
| Attribute | Type | Required | Description |
|-------------|---------|----------|------------------------------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `issue_iid` | integer | yes | The internal ID of a project's issue |
| `duration` | string | yes | The duration in human format. e.g: 3h30m |
@@ -779,7 +779,7 @@ POST /projects/:id/issues/:issue_iid/reset_spent_time
| Attribute | Type | Required | Description |
|-------------|---------|----------|--------------------------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash
@@ -805,7 +805,7 @@ GET /projects/:id/issues/:issue_iid/time_stats
| Attribute | Type | Required | Description |
|-------------|---------|----------|--------------------------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash
diff --git a/doc/api/jobs.md b/doc/api/jobs.md
index 7340123e09d..bea2b96c97a 100644
--- a/doc/api/jobs.md
+++ b/doc/api/jobs.md
@@ -10,7 +10,7 @@ GET /projects/:id/jobs
| Attribute | Type | Required | Description |
|-----------|---------|----------|---------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `scope` | string **or** array of strings | no | The scope of jobs to show, one or array of: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`; showing all jobs if none provided |
```
@@ -125,7 +125,7 @@ GET /projects/:id/pipeline/:pipeline_id/jobs
| Attribute | Type | Required | Description |
|---------------|--------------------------------|----------|----------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `pipeline_id` | integer | yes | The ID of a pipeline |
| `scope` | string **or** array of strings | no | The scope of jobs to show, one or array of: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`; showing all jobs if none provided |
@@ -241,7 +241,7 @@ GET /projects/:id/jobs/:job_id
| Attribute | Type | Required | Description |
|------------|---------|----------|---------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `job_id` | integer | yes | The ID of a job |
```
@@ -309,7 +309,7 @@ GET /projects/:id/jobs/:job_id/artifacts
| Attribute | Type | Required | Description |
|------------|---------|----------|---------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `job_id` | integer | yes | The ID of a job |
```
@@ -340,7 +340,7 @@ Parameters
| Attribute | Type | Required | Description |
|-------------|---------|----------|-------------------------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `ref_name` | string | yes | The ref from a repository |
| `job` | string | yes | The name of the job |
@@ -369,7 +369,7 @@ GET /projects/:id/jobs/:job_id/trace
| Attribute | Type | Required | Description |
|------------|---------|----------|---------------------|
-| id | integer | yes | The ID of a project |
+| id | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| job_id | integer | yes | The ID of a job |
```
@@ -393,7 +393,7 @@ POST /projects/:id/jobs/:job_id/cancel
| Attribute | Type | Required | Description |
|------------|---------|----------|---------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `job_id` | integer | yes | The ID of a job |
```
@@ -439,7 +439,7 @@ POST /projects/:id/jobs/:job_id/retry
| Attribute | Type | Required | Description |
|------------|---------|----------|---------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `job_id` | integer | yes | The ID of a job |
```
@@ -487,7 +487,7 @@ Parameters
| Attribute | Type | Required | Description |
|-------------|---------|----------|---------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `job_id` | integer | yes | The ID of a job |
Example of request
@@ -537,7 +537,7 @@ Parameters
| Attribute | Type | Required | Description |
|-------------|---------|----------|---------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `job_id` | integer | yes | The ID of a job |
Example request:
@@ -585,7 +585,7 @@ POST /projects/:id/jobs/:job_id/play
| Attribute | Type | Required | Description |
|-----------|---------|----------|---------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `job_id` | integer | yes | The ID of a job |
```
diff --git a/doc/api/labels.md b/doc/api/labels.md
index 839000a4f48..778348ea371 100644
--- a/doc/api/labels.md
+++ b/doc/api/labels.md
@@ -10,7 +10,7 @@ GET /projects/:id/labels
| Attribute | Type | Required | Description |
| --------- | ------- | -------- | --------------------- |
-| `id` | integer | yes | The ID of the project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/labels
@@ -88,7 +88,7 @@ POST /projects/:id/labels
| Attribute | Type | Required | Description |
| ------------- | ------- | -------- | ---------------------------- |
-| `id` | integer | yes | The ID of the project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `name` | string | yes | The name of the label |
| `color` | string | yes | The color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) or one of the [CSS color names](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#Color_keywords) |
| `description` | string | no | The description of the label |
@@ -124,7 +124,7 @@ DELETE /projects/:id/labels
| Attribute | Type | Required | Description |
| --------- | ------- | -------- | --------------------- |
-| `id` | integer | yes | The ID of the project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `name` | string | yes | The name of the label |
```bash
@@ -142,7 +142,7 @@ PUT /projects/:id/labels
| Attribute | Type | Required | Description |
| --------------- | ------- | --------------------------------- | ------------------------------- |
-| `id` | integer | yes | The ID of the project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `name` | string | yes | The name of the existing label |
| `new_name` | string | yes if `color` is not provided | The new name of the label |
| `color` | string | yes if `new_name` is not provided | The color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) or one of the [CSS color names](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#Color_keywords) |
@@ -182,7 +182,7 @@ POST /projects/:id/labels/:label_id/subscribe
| Attribute | Type | Required | Description |
| ---------- | ----------------- | -------- | ------------------------------------ |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `label_id` | integer or string | yes | The ID or title of a project's label |
```bash
@@ -217,7 +217,7 @@ POST /projects/:id/labels/:label_id/unsubscribe
| Attribute | Type | Required | Description |
| ---------- | ----------------- | -------- | ------------------------------------ |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `label_id` | integer or string | yes | The ID or title of a project's label |
```bash
diff --git a/doc/api/members.md b/doc/api/members.md
index fe46f8f84bc..3c661284f11 100644
--- a/doc/api/members.md
+++ b/doc/api/members.md
@@ -23,7 +23,7 @@ GET /projects/:id/members
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The group/project ID or path |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project or group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `query` | string | no | A query string to search for members |
```bash
@@ -65,7 +65,7 @@ GET /projects/:id/members/:user_id
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The group/project ID or path |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project or group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `user_id` | integer | yes | The user ID of the member |
```bash
@@ -98,7 +98,7 @@ POST /projects/:id/members
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The group/project ID or path |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project or group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `user_id` | integer | yes | The user ID of the new member |
| `access_level` | integer | yes | A valid access level |
| `expires_at` | string | no | A date string in the format YEAR-MONTH-DAY |
@@ -132,7 +132,7 @@ PUT /projects/:id/members/:user_id
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The group/project ID or path |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project or group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `user_id` | integer | yes | The user ID of the member |
| `access_level` | integer | yes | A valid access level |
| `expires_at` | string | no | A date string in the format YEAR-MONTH-DAY |
@@ -166,7 +166,7 @@ DELETE /projects/:id/members/:user_id
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The group/project ID or path |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project or group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `user_id` | integer | yes | The user ID of the member |
```bash
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 2e0545da1c4..ff956add348 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -15,7 +15,7 @@ GET /projects/:id/merge_requests?iids[]=42&iids[]=43
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `iid` (optional) - Return the request having the given `iid`
- `state` (optional) - Return `all` requests or just those that are `merged`, `opened` or `closed`
- `order_by` (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at`
@@ -87,7 +87,7 @@ GET /projects/:id/merge_requests/:merge_request_iid
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `merge_request_iid` (required) - The internal ID of the merge request
```json
@@ -155,7 +155,7 @@ GET /projects/:id/merge_requests/:merge_request_iid/commits
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `merge_request_iid` (required) - The internal ID of the merge request
@@ -192,7 +192,7 @@ GET /projects/:id/merge_requests/:merge_request_iid/changes
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `merge_request_iid` (required) - The internal ID of the merge request
```json
@@ -271,7 +271,7 @@ POST /projects/:id/merge_requests
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | string | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `source_branch` | string | yes | The source branch |
| `target_branch` | string | yes | The target branch |
| `title` | string | yes | Title of MR |
@@ -347,7 +347,7 @@ PUT /projects/:id/merge_requests/:merge_request_iid
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | string | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `merge_request_iid` | integer | yes | The ID of a merge request |
| `target_branch` | string | no | The target branch |
| `title` | string | no | Title of MR |
@@ -422,9 +422,9 @@ Only for admins and project owners. Soft deletes the merge request in question.
DELETE /projects/:id/merge_requests/:merge_request_iid
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `merge_request_iid` | integer | yes | The internal ID of the merge request |
```bash
@@ -450,7 +450,7 @@ PUT /projects/:id/merge_requests/:merge_request_iid/merge
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `merge_request_iid` (required) - Internal ID of MR
- `merge_commit_message` (optional) - Custom merge commit message
- `should_remove_source_branch` (optional) - if `true` removes the source branch
@@ -524,7 +524,7 @@ PUT /projects/:id/merge_requests/:merge_request_iid/cancel_merge_when_pipeline_s
```
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `merge_request_iid` (required) - Internal ID of MR
```json
@@ -596,7 +596,7 @@ GET /projects/:id/merge_requests/:merge_request_iid/closes_issues
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `merge_request_iid` | integer | yes | The internal ID of the merge request |
```bash
@@ -671,7 +671,7 @@ POST /projects/:id/merge_requests/:merge_request_iid/subscribe
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `merge_request_iid` | integer | yes | The internal ID of the merge request |
```bash
@@ -745,7 +745,7 @@ POST /projects/:id/merge_requests/:merge_request_iid/unsubscribe
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `merge_request_iid` | integer | yes | The internal ID of the merge request |
```bash
@@ -819,7 +819,7 @@ POST /projects/:id/merge_requests/:merge_request_iid/todo
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `merge_request_iid` | integer | yes | The internal ID of the merge request |
```bash
@@ -1027,7 +1027,7 @@ POST /projects/:id/merge_requests/:merge_request_iid/time_estimate
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `merge_request_iid` | integer | yes | The internal ID of the merge request |
| `duration` | string | yes | The duration in human format. e.g: 3h30m |
@@ -1056,7 +1056,7 @@ POST /projects/:id/merge_requests/:merge_request_iid/reset_time_estimate
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `merge_request_iid` | integer | yes | The internal ID of a project's merge_request |
```bash
@@ -1084,7 +1084,7 @@ POST /projects/:id/merge_requests/:merge_request_iid/add_spent_time
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `merge_request_iid` | integer | yes | The internal ID of the merge request |
| `duration` | string | yes | The duration in human format. e.g: 3h30m |
@@ -1113,7 +1113,7 @@ POST /projects/:id/merge_requests/:merge_request_iid/reset_spent_time
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `merge_request_iid` | integer | yes | The internal ID of a project's merge_request |
```bash
@@ -1139,7 +1139,7 @@ GET /projects/:id/merge_requests/:merge_request_iid/time_stats
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `merge_request_iid` | integer | yes | The internal ID of the merge request |
```bash
diff --git a/doc/api/milestones.md b/doc/api/milestones.md
index 3c86357a6c3..7640eeb8d00 100644
--- a/doc/api/milestones.md
+++ b/doc/api/milestones.md
@@ -17,7 +17,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `iids` | Array[integer] | optional | Return only the milestones having the given `iids` |
| `state` | string | optional | Return only `active` or `closed` milestones` |
| `search` | string | optional | Return only milestones with a title or description matching the provided string |
@@ -56,8 +56,8 @@ GET /projects/:id/milestones/:milestone_id
Parameters:
-- `id` (required) - The ID of a project
-- `milestone_id` (required) - The ID of a project milestone
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
+- `milestone_id` (required) - The ID of the project's milestone
## Create new milestone
@@ -69,7 +69,7 @@ POST /projects/:id/milestones
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `title` (required) - The title of an milestone
- `description` (optional) - The description of the milestone
- `due_date` (optional) - The due date of the milestone
@@ -85,7 +85,7 @@ PUT /projects/:id/milestones/:milestone_id
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `milestone_id` (required) - The ID of a project milestone
- `title` (optional) - The title of a milestone
- `description` (optional) - The description of a milestone
@@ -103,7 +103,7 @@ GET /projects/:id/milestones/:milestone_id/issues
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `milestone_id` (required) - The ID of a project milestone
## Get all merge requests assigned to a single milestone
@@ -116,5 +116,5 @@ GET /projects/:id/milestones/:milestone_id/merge_requests
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `milestone_id` (required) - The ID of a project milestone
diff --git a/doc/api/notes.md b/doc/api/notes.md
index 5e927143714..b71fea5fc9f 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -14,7 +14,7 @@ GET /projects/:id/issues/:issue_iid/notes
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `issue_iid` (required) - The IID of an issue
```json
@@ -68,7 +68,7 @@ GET /projects/:id/issues/:issue_iid/notes/:note_id
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `issue_iid` (required) - The IID of a project issue
- `note_id` (required) - The ID of an issue note
@@ -83,7 +83,7 @@ POST /projects/:id/issues/:issue_iid/notes
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `issue_id` (required) - The IID of an issue
- `body` (required) - The content of a note
- `created_at` (optional) - Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z
@@ -98,7 +98,7 @@ PUT /projects/:id/issues/:issue_iid/notes/:note_id
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `issue_iid` (required) - The IID of an issue
- `note_id` (required) - The ID of a note
- `body` (required) - The content of a note
@@ -115,7 +115,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `issue_iid` | integer | yes | The IID of an issue |
| `note_id` | integer | yes | The ID of a note |
@@ -135,7 +135,7 @@ GET /projects/:id/snippets/:snippet_id/notes
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `snippet_id` (required) - The ID of a project snippet
### Get single snippet note
@@ -148,7 +148,7 @@ GET /projects/:id/snippets/:snippet_id/notes/:note_id
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `snippet_id` (required) - The ID of a project snippet
- `note_id` (required) - The ID of an snippet note
@@ -182,7 +182,7 @@ POST /projects/:id/snippets/:snippet_id/notes
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `snippet_id` (required) - The ID of a snippet
- `body` (required) - The content of a note
@@ -196,7 +196,7 @@ PUT /projects/:id/snippets/:snippet_id/notes/:note_id
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `snippet_id` (required) - The ID of a snippet
- `note_id` (required) - The ID of a note
- `body` (required) - The content of a note
@@ -213,7 +213,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `snippet_id` | integer | yes | The ID of a snippet |
| `note_id` | integer | yes | The ID of a note |
@@ -233,7 +233,7 @@ GET /projects/:id/merge_requests/:merge_request_iid/notes
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `merge_request_iid` (required) - The IID of a project merge request
### Get single merge request note
@@ -246,7 +246,7 @@ GET /projects/:id/merge_requests/:merge_request_iid/notes/:note_id
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `merge_request_iid` (required) - The IID of a project merge request
- `note_id` (required) - The ID of a merge request note
@@ -283,7 +283,7 @@ POST /projects/:id/merge_requests/:merge_request_iid/notes
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `merge_request_iid` (required) - The IID of a merge request
- `body` (required) - The content of a note
@@ -297,7 +297,7 @@ PUT /projects/:id/merge_requests/:merge_request_iid/notes/:note_id
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `merge_request_iid` (required) - The IID of a merge request
- `note_id` (required) - The ID of a note
- `body` (required) - The content of a note
@@ -314,7 +314,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `merge_request_iid` | integer | yes | The IID of a merge request |
| `note_id` | integer | yes | The ID of a note |
diff --git a/doc/api/pipeline_triggers.md b/doc/api/pipeline_triggers.md
index 50fc19f0e08..d639e8a0991 100644
--- a/doc/api/pipeline_triggers.md
+++ b/doc/api/pipeline_triggers.md
@@ -12,7 +12,7 @@ GET /projects/:id/triggers
| Attribute | Type | required | Description |
|-----------|---------|----------|---------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
```
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers"
@@ -43,7 +43,7 @@ GET /projects/:id/triggers/:trigger_id
| Attribute | Type | required | Description |
|--------------|---------|----------|--------------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `trigger_id` | integer | yes | The trigger id |
```
@@ -73,7 +73,7 @@ POST /projects/:id/triggers
| Attribute | Type | required | Description |
|---------------|---------|----------|--------------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `description` | string | yes | The trigger name |
```
@@ -103,7 +103,7 @@ PUT /projects/:id/triggers/:trigger_id
| Attribute | Type | required | Description |
|---------------|---------|----------|--------------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `trigger_id` | integer | yes | The trigger id |
| `description` | string | no | The trigger name |
@@ -134,7 +134,7 @@ POST /projects/:id/triggers/:trigger_id/take_ownership
| Attribute | Type | required | Description |
|---------------|---------|----------|--------------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `trigger_id` | integer | yes | The trigger id |
```
@@ -164,7 +164,7 @@ DELETE /projects/:id/triggers/:trigger_id
| Attribute | Type | required | Description |
|----------------|---------|----------|--------------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `trigger_id` | integer | yes | The trigger id |
```
diff --git a/doc/api/pipelines.md b/doc/api/pipelines.md
index 574a8bacb25..732ad8da4ac 100644
--- a/doc/api/pipelines.md
+++ b/doc/api/pipelines.md
@@ -10,7 +10,7 @@ GET /projects/:id/pipelines
| Attribute | Type | Required | Description |
|-----------|---------|----------|---------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
```
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/pipelines"
@@ -45,7 +45,7 @@ GET /projects/:id/pipelines/:pipeline_id
| Attribute | Type | Required | Description |
|------------|---------|----------|---------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `pipeline_id` | integer | yes | The ID of a pipeline |
```
@@ -91,7 +91,7 @@ POST /projects/:id/pipeline
| Attribute | Type | Required | Description |
|------------|---------|----------|---------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `ref` | string | yes | Reference to commit |
```
@@ -137,7 +137,7 @@ POST /projects/:id/pipelines/:pipeline_id/retry
| Attribute | Type | Required | Description |
|------------|---------|----------|---------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `pipeline_id` | integer | yes | The ID of a pipeline |
```
@@ -173,7 +173,7 @@ Response:
}
```
-## Cancel a pipelines jobs
+## Cancel a pipelines jobs
> [Introduced][ce-5837] in GitLab 8.11
@@ -183,7 +183,7 @@ POST /projects/:id/pipelines/:pipeline_id/cancel
| Attribute | Type | Required | Description |
|------------|---------|----------|---------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `pipeline_id` | integer | yes | The ID of a pipeline |
```
diff --git a/doc/api/project_snippets.md b/doc/api/project_snippets.md
index 4f6f561b83e..ff379473961 100644
--- a/doc/api/project_snippets.md
+++ b/doc/api/project_snippets.md
@@ -23,7 +23,7 @@ GET /projects/:id/snippets
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
## Single snippet
@@ -35,7 +35,7 @@ GET /projects/:id/snippets/:snippet_id
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `snippet_id` (required) - The ID of a project's snippet
```json
@@ -67,7 +67,7 @@ POST /projects/:id/snippets
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `title` (required) - The title of a snippet
- `file_name` (required) - The name of a snippet file
- `code` (required) - The content of a snippet
@@ -83,7 +83,7 @@ PUT /projects/:id/snippets/:snippet_id
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `snippet_id` (required) - The ID of a project's snippet
- `title` (optional) - The title of a snippet
- `file_name` (optional) - The name of a snippet file
@@ -101,7 +101,7 @@ DELETE /projects/:id/snippets/:snippet_id
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `snippet_id` (required) - The ID of a project's snippet
## Snippet content
@@ -114,5 +114,5 @@ GET /projects/:id/snippets/:snippet_id/raw
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `snippet_id` (required) - The ID of a project's snippet
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 686f3dba35d..63f88a464f5 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -18,6 +18,7 @@ Constants for project visibility levels are next:
The project can be cloned without any authentication.
+
## List projects
Get a list of visible projects for authenticated user. When being accessed without authentication, all public projects are returned.
@@ -157,8 +158,7 @@ Parameters:
### Get single project
-Get a specific project, identified by project ID or NAMESPACE/PROJECT_NAME, which is owned by the authenticated user.
-If using namespaced projects call make sure that the NAMESPACE/PROJECT_NAME is URL-encoded, eg. `/api/v3/projects/diaspora%2Fdiaspora` (where `/` is represented by `%2F`). This endpoint can be accessed without authentication if
+Get a specific project. This endpoint can be accessed without authentication if
the project is publicly accessible.
```
@@ -169,7 +169,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or NAMESPACE/PROJECT_NAME of the project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
```json
{
@@ -295,7 +295,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or NAMESPACE/PROJECT_NAME of the project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
```json
[
@@ -497,7 +497,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or NAMESPACE/PROJECT_NAME of the project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `name` | string | yes | The name of the project |
| `path` | string | no | Custom repository name for the project. By default generated based on name |
| `default_branch` | string | no | `master` by default |
@@ -529,7 +529,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or NAMESPACE/PROJECT_NAME of the project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `namespace` | integer/string | yes | The ID or path of the namespace that the project will be forked to |
### Star a project
@@ -544,7 +544,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or NAMESPACE/PROJECT_NAME of the project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/star"
@@ -609,7 +609,7 @@ POST /projects/:id/unstar
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/unstar"
@@ -675,7 +675,7 @@ POST /projects/:id/archive
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/archive"
@@ -757,7 +757,7 @@ POST /projects/:id/unarchive
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/unarchive"
@@ -840,7 +840,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
## Uploads
@@ -856,7 +856,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `file` | string | yes | The file to be uploaded |
```json
@@ -887,7 +887,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `group_id` | integer | yes | The ID of the group to share with |
| `group_access` | integer | yes | The permissions level to grant the group |
| `expires_at` | string | no | Share expiration date in ISO 8601 format: 2016-09-26 |
@@ -904,7 +904,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `group_id` | integer | yes | The ID of the group |
```bash
@@ -928,7 +928,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
### Get project hook
@@ -942,7 +942,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `hook_id` | integer | yes | The ID of a project hook |
```json
@@ -975,7 +975,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `url` | string | yes | The hook URL |
| `push_events` | boolean | no | Trigger hook on push events |
| `issues_events` | boolean | no | Trigger hook on issues events |
@@ -1000,7 +1000,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `hook_id` | integer | yes | The ID of the project hook |
| `url` | string | yes | The hook URL |
| `push_events` | boolean | no | Trigger hook on push events |
@@ -1027,7 +1027,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `hook_id` | integer | yes | The ID of the project hook |
Note the JSON response differs if the hook is available or not. If the project hook
@@ -1049,7 +1049,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
```json
[
@@ -1106,7 +1106,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `branch` | string | yes | The name of the branch |
| `developers_can_push` | boolean | no | Flag if developers can push to the branch |
| `developers_can_merge` | boolean | no | Flag if developers can merge to the branch |
@@ -1123,7 +1123,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `branch` | string | yes | The name of the branch |
### Unprotect single branch
@@ -1138,7 +1138,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `branch` | string | yes | The name of the branch |
## Admin fork relation
@@ -1155,7 +1155,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `forked_from_id` | ID | yes | The ID of the project that was forked from |
### Delete an existing forked from relationship
@@ -1168,7 +1168,7 @@ Parameter:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
## Search for projects by name
diff --git a/doc/api/repositories.md b/doc/api/repositories.md
index b1bf9ca07cc..859cbd63831 100644
--- a/doc/api/repositories.md
+++ b/doc/api/repositories.md
@@ -13,7 +13,7 @@ GET /projects/:id/repository/tree
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `path` (optional) - The path inside repository. Used to get contend of subdirectories
- `ref` (optional) - The name of a repository branch or tag or if not given the default branch
- `recursive` (optional) - Boolean value used to get a recursive tree (false by default)
@@ -84,7 +84,7 @@ GET /projects/:id/repository/blobs/:sha
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `sha` (required) - The commit or branch name
## Raw blob content
@@ -98,7 +98,7 @@ GET /projects/:id/repository/blobs/:sha/raw
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `sha` (required) - The blob SHA
## Get file archive
@@ -112,7 +112,7 @@ GET /projects/:id/repository/archive
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `sha` (optional) - The commit SHA to download defaults to the tip of the default branch
## Compare branches, tags or commits
@@ -126,7 +126,7 @@ GET /projects/:id/repository/compare
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `from` (required) - the commit SHA or branch name
- `to` (required) - the commit SHA or branch name
@@ -181,7 +181,7 @@ GET /projects/:id/repository/contributors
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
Response:
diff --git a/doc/api/runners.md b/doc/api/runners.md
index 46f882ce937..16d362a3530 100644
--- a/doc/api/runners.md
+++ b/doc/api/runners.md
@@ -222,7 +222,7 @@ GET /projects/:id/runners
| Attribute | Type | Required | Description |
|-----------|---------|----------|---------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
```
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/9/runners"
@@ -259,7 +259,7 @@ POST /projects/:id/runners
| Attribute | Type | Required | Description |
|-------------|---------|----------|---------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `runner_id` | integer | yes | The ID of a runner |
```
@@ -290,7 +290,7 @@ DELETE /projects/:id/runners/:runner_id
| Attribute | Type | Required | Description |
|-------------|---------|----------|---------------------|
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `runner_id` | integer | yes | The ID of a runner |
```
diff --git a/doc/api/tags.md b/doc/api/tags.md
index bf350f024f5..0f6c4e6794e 100644
--- a/doc/api/tags.md
+++ b/doc/api/tags.md
@@ -12,7 +12,7 @@ GET /projects/:id/repository/tags
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
```json
[
@@ -53,7 +53,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `tag_name` | string | yes | The name of the tag |
```bash
@@ -93,7 +93,7 @@ POST /projects/:id/repository/tags
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `tag_name` (required) - The name of a tag
- `ref` (required) - Create tag using commit SHA, another tag name, or branch name.
- `message` (optional) - Creates annotated tag.
@@ -138,7 +138,7 @@ DELETE /projects/:id/repository/tags/:tag_name
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `tag_name` (required) - The name of a tag
@@ -153,7 +153,7 @@ POST /projects/:id/repository/tags/:tag_name/release
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `tag_name` (required) - The name of a tag
- `description` (required) - Release notes with markdown support
@@ -174,7 +174,7 @@ PUT /projects/:id/repository/tags/:tag_name/release
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `tag_name` (required) - The name of a tag
- `description` (required) - Release notes with markdown support
diff --git a/doc/ci/README.md b/doc/ci/README.md
index b3780a08828..c4f9a3cb573 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -110,9 +110,8 @@ Here is an collection of tutorials and guides on setting up your CI pipeline.
- [Run PHP Composer & NPM scripts then deploy them to a staging server](examples/deployment/composer-npm-deploy.md)
- **Blog posts**
- [Automated Debian packaging](https://about.gitlab.com/2016/10/12/automated-debian-package-build-with-gitlab-ci/)
- - [Spring boot application with GitLab CI and Kubernetes](https://about.gitlab.com/2016/11/30/setting-up-gitlab-ci-for-android-projects/)
- - [Setting up CI for iOS projects](https://about.gitlab.com/2016/12/14/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/)
- - [Using GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/)
+ - [Spring boot application with GitLab CI and Kubernetes](https://about.gitlab.com/2016/12/14/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/)
+ - [Setting up GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/)
- [Setting up GitLab CI for Android projects](https://about.gitlab.com/2016/11/30/setting-up-gitlab-ci-for-android-projects/)
- [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/)
- [CI/CD with GitLab in action](https://about.gitlab.com/2017/03/13/ci-cd-demo/)
diff --git a/doc/ci/img/pipelines.png b/doc/ci/img/pipelines.png
index 5937e9d99c8..a604fcb2587 100644
--- a/doc/ci/img/pipelines.png
+++ b/doc/ci/img/pipelines.png
Binary files differ
diff --git a/doc/development/fe_guide/performance.md b/doc/development/fe_guide/performance.md
index 9437a5f7a6e..2ddcbe13afa 100644
--- a/doc/development/fe_guide/performance.md
+++ b/doc/development/fe_guide/performance.md
@@ -12,8 +12,8 @@ Thus, we must strike a balance between sending requests and the feeling of realt
Use the following rules when creating realtime solutions.
1. The server will tell you how much to poll by sending `Poll-Interval` in the header.
-Use that as your polling interval. This way it is easy for system administrators to change the
-polling rate.
+Use that as your polling interval. This way it is [easy for system administrators to change the
+polling rate](../../administration/polling.md).
A `Poll-Interval: -1` means you should disable polling, and this must be implemented.
1. A response with HTTP status `4XX` or `5XX` should disable polling as well.
1. Use a common library for polling.
@@ -48,8 +48,8 @@ Steps to split page-specific JavaScript from the main `main.js`:
```haml
- content_for :page_specific_javascripts do
- = page_specific_javascript_bundle_tag('lib_chart')
- = page_specific_javascript_bundle_tag('graphs')
+ = webpack_bundle_tag 'lib_chart'
+ = webpack_bundle_tag 'graphs'
```
The above loads `chart.js` and `graphs_bundle.js` for this page only. `chart.js`
diff --git a/doc/development/fe_guide/testing.md b/doc/development/fe_guide/testing.md
index 8d3513d3566..a4631fd0073 100644
--- a/doc/development/fe_guide/testing.md
+++ b/doc/development/fe_guide/testing.md
@@ -13,10 +13,19 @@ for more information on general testing practices at GitLab.
## Karma test suite
GitLab uses the [Karma][karma] test runner with [Jasmine][jasmine] as its test
-framework for our JavaScript unit tests. For tests that rely on DOM
+framework for our JavaScript unit tests. For tests that rely on DOM
manipulation we use fixtures which are pre-compiled from HAML source files and
served during testing by the [jasmine-jquery][jasmine-jquery] plugin.
+JavaScript tests live in `spec/javascripts/`, matching the folder structure
+of `app/assets/javascripts/`: `app/assets/javascripts/behaviors/autosize.js`
+has a corresponding `spec/javascripts/behaviors/autosize_spec.js` file.
+
+Keep in mind that in a CI environment, these tests are run in a headless
+browser and you will not have access to certain APIs, such as
+[`Notification`](https://developer.mozilla.org/en-US/docs/Web/API/notification),
+which will have to be stubbed.
+
### Running frontend tests
`rake karma` runs the frontend-only (JavaScript) tests.
@@ -80,24 +89,23 @@ If an integration test depends on JavaScript to run correctly, you need to make
sure the spec is configured to enable JavaScript when the tests are run. If you
don't do this you'll see vague error messages from the spec runner.
-To enable a JavaScript driver in an `rspec` test, add `js: true` to the
+To enable a JavaScript driver in an `rspec` test, add `:js` to the
individual spec or the context block containing multiple specs that need
JavaScript enabled:
```ruby
-
# For one spec
-it 'presents information about abuse report', js: true do
- # assertions...
+it 'presents information about abuse report', :js do
+ # assertions...
end
-describe "Admin::AbuseReports", js: true do
- it 'presents information about abuse report' do
- # assertions...
- end
- it 'shows buttons for adding to abuse report' do
- # assertions...
- end
+describe "Admin::AbuseReports", :js do
+ it 'presents information about abuse report' do
+ # assertions...
+ end
+ it 'shows buttons for adding to abuse report' do
+ # assertions...
+ end
end
```
@@ -113,13 +121,12 @@ file for the failing spec, add the `@javascript` flag above the Scenario:
```
@javascript
Scenario: Developer can approve merge request
- Given I am a "Shop" developer
- And I visit project "Shop" merge requests page
- And merge request 'Bug NS-04' must be approved
- And I click link "Bug NS-04"
- When I click link "Approve"
- Then I should see approved merge request "Bug NS-04"
-
+ Given I am a "Shop" developer
+ And I visit project "Shop" merge requests page
+ And merge request 'Bug NS-04' must be approved
+ And I click link "Bug NS-04"
+ When I click link "Approve"
+ Then I should see approved merge request "Bug NS-04"
```
[capybara]: http://teamcapybara.github.io/capybara/
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index 587922d0136..3e8b709c18f 100644
--- a/doc/development/migration_style_guide.md
+++ b/doc/development/migration_style_guide.md
@@ -4,28 +4,53 @@ When writing migrations for GitLab, you have to take into account that
these will be ran by hundreds of thousands of organizations of all sizes, some with
many years of data in their database.
-In addition, having to take a server offline for a an upgrade small or big is
-a big burden for most organizations. For this reason it is important that your
-migrations are written carefully, can be applied online and adhere to the style guide below.
+In addition, having to take a server offline for a a upgrade small or big is a
+big burden for most organizations. For this reason it is important that your
+migrations are written carefully, can be applied online and adhere to the style
+guide below.
-Migrations should not require GitLab installations to be taken offline unless
-_absolutely_ necessary - see the ["What Requires Downtime?"](what_requires_downtime.md)
-page. If a migration requires downtime, this should be clearly mentioned during
-the review process, as well as being documented in the monthly release post. For
-more information, see the "Downtime Tagging" section below.
+Migrations are **not** allowed to require GitLab installations to be taken
+offline unless _absolutely necessary_. Downtime assumptions should be based on
+the behaviour of a migration when performed using PostgreSQL, as various
+operations in MySQL may require downtime without there being alternatives.
+
+When downtime is necessary the migration has to be approved by:
+
+1. The VP of Engineering
+1. A Backend Lead
+1. A Database Specialist
+
+An up-to-date list of people holding these titles can be found at
+<https://about.gitlab.com/team/>.
+
+The document ["What Requires Downtime?"](what_requires_downtime.md) specifies
+various database operations, whether they require downtime and how to
+work around that whenever possible.
When writing your migrations, also consider that databases might have stale data
-or inconsistencies and guard for that. Try to make as little assumptions as possible
-about the state of the database.
+or inconsistencies and guard for that. Try to make as few assumptions as
+possible about the state of the database.
+
+Please don't depend on GitLab-specific code since it can change in future
+versions. If needed copy-paste GitLab code into the migration to make it forward
+compatible.
+
+## Commit Guidelines
-Please don't depend on GitLab specific code since it can change in future versions.
-If needed copy-paste GitLab code into the migration to make it forward compatible.
+Each migration **must** be added in its own commit with a descriptive commit
+message. If a commit adds a migration it _should only_ include the migration and
+any corresponding changes to `db/schema.rb`. This makes it easy to revert a
+database migration without accidentally reverting other changes.
## Downtime Tagging
Every migration must specify if it requires downtime or not, and if it should
-require downtime it must also specify a reason for this. To do so, add the
-following two constants to the migration class' body:
+require downtime it must also specify a reason for this. This is required even
+if 99% of the migrations won't require downtime as this makes it easier to find
+the migrations that _do_ require downtime.
+
+To tag a migration, add the following two constants to the migration class'
+body:
* `DOWNTIME`: a boolean that when set to `true` indicates the migration requires
downtime.
@@ -50,12 +75,53 @@ from a migration class.
## Reversibility
-Your migration should be reversible. This is very important, as it should
+Your migration **must be** reversible. This is very important, as it should
be possible to downgrade in case of a vulnerability or bugs.
In your migration, add a comment describing how the reversibility of the
migration was tested.
+## Multi Threading
+
+Sometimes a migration might need to use multiple Ruby threads to speed up a
+migration. For this to work your migration needs to include the module
+`Gitlab::Database::MultiThreadedMigration`:
+
+```ruby
+class MyMigration < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+ include Gitlab::Database::MultiThreadedMigration
+end
+```
+
+You can then use the method `with_multiple_threads` to perform work in separate
+threads. For example:
+
+```ruby
+class MyMigration < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+ include Gitlab::Database::MultiThreadedMigration
+
+ def up
+ with_multiple_threads(4) do
+ disable_statement_timeout
+
+ # ...
+ end
+ end
+end
+```
+
+Here the call to `disable_statement_timeout` will use the connection local to
+the `with_multiple_threads` block, instead of re-using the global connection
+pool. This ensures each thread has its own connection object, and won't time
+out when trying to obtain one.
+
+**NOTE:** PostgreSQL has a maximum amount of connections that it allows. This
+limit can vary from installation to installation. As a result it's recommended
+you do not use more than 32 threads in a single migration. Usually 4-8 threads
+should be more than enough.
+
## Removing indices
When removing an index make sure to use the method `remove_concurrent_index` instead
@@ -78,7 +144,10 @@ end
## Adding indices
-If you need to add an unique index please keep in mind there is possibility of existing duplicates. If it is possible write a separate migration for handling this situation. It can be just removing or removing with overwriting all references to these duplicates depend on situation.
+If you need to add a unique index please keep in mind there is the possibility
+of existing duplicates being present in the database. This means that should
+always _first_ add a migration that removes any duplicates, before adding the
+unique index.
When adding an index make sure to use the method `add_concurrent_index` instead
of the regular `add_index` method. The `add_concurrent_index` method
@@ -90,17 +159,22 @@ so:
```ruby
class MyMigration < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
+
disable_ddl_transaction!
- def change
+ def up
+ add_concurrent_index :table, :column
+ end
+ def down
+ remove_index :table, :column if index_exists?(:table, :column)
end
end
```
## Adding Columns With Default Values
-When adding columns with default values you should use the method
+When adding columns with default values you must use the method
`add_column_with_default`. This method ensures the table is updated without
requiring downtime. This method is not reversible so you must manually define
the `up` and `down` methods in your migration class.
@@ -123,6 +197,9 @@ class MyMigration < ActiveRecord::Migration
end
```
+Keep in mind that this operation can easily take 10-15 minutes to complete on
+larger installations (e.g. GitLab.com). As a result you should only add default
+values if absolutely necessary.
## Integer column type
@@ -147,13 +224,15 @@ add_column(:projects, :foo, :integer, default: 10, limit: 8)
## Testing
-Make sure that your migration works with MySQL and PostgreSQL with data. An empty database does not guarantee that your migration is correct.
+Make sure that your migration works with MySQL and PostgreSQL with data. An
+empty database does not guarantee that your migration is correct.
Make sure your migration can be reversed.
## Data migration
-Please prefer Arel and plain SQL over usual ActiveRecord syntax. In case of using plain SQL you need to quote all input manually with `quote_string` helper.
+Please prefer Arel and plain SQL over usual ActiveRecord syntax. In case of
+using plain SQL you need to quote all input manually with `quote_string` helper.
Example with Arel:
@@ -177,3 +256,17 @@ select_all("SELECT name, COUNT(id) as cnt FROM tags GROUP BY name HAVING COUNT(i
execute("DELETE FROM tags WHERE id IN(#{duplicate_ids.join(",")})")
end
```
+
+If you need more complex logic you can define and use models local to a
+migration. For example:
+
+```ruby
+class MyMigration < ActiveRecord::Migration
+ class Project < ActiveRecord::Base
+ self.table_name = 'projects'
+ end
+end
+```
+
+When doing so be sure to explicitly set the model's table name so it's not
+derived from the class name or namespace.
diff --git a/doc/development/polling.md b/doc/development/polling.md
index 05e19f0c515..3b34f985cd4 100644
--- a/doc/development/polling.md
+++ b/doc/development/polling.md
@@ -51,5 +51,6 @@ request path. By doing this we avoid query parameter ordering problems and make
route matching easier.
For more information see:
+- [`Poll-Interval` header](fe_guide/performance.md#realtime-components)
- [RFC 7232](https://tools.ietf.org/html/rfc7232)
- [ETag proposal](https://gitlab.com/gitlab-org/gitlab-ce/issues/26926)
diff --git a/doc/development/testing.md b/doc/development/testing.md
index 5bc958f5a96..ad540ec13db 100644
--- a/doc/development/testing.md
+++ b/doc/development/testing.md
@@ -9,52 +9,179 @@ this guide defines a rule that contradicts the thoughtbot guide, this guide
takes precedence. Some guidelines may be repeated verbatim to stress their
importance.
-## Factories
+## Definitions
+
+### Unit tests
+
+Formal definition: https://en.wikipedia.org/wiki/Unit_testing
+
+These kind of tests ensure that a single unit of code (a method) works as
+expected (given an input, it has a predictable output). These tests should be
+isolated as much as possible. For example, model methods that don't do anything
+with the database shouldn't need a DB record. Classes that don't need database
+records should use stubs/doubles as much as possible.
+
+| Code path | Tests path | Testing engine | Notes |
+| --------- | ---------- | -------------- | ----- |
+| `app/finders/` | `spec/finders/` | RSpec | |
+| `app/helpers/` | `spec/helpers/` | RSpec | |
+| `app/db/{post_,}migrate/` | `spec/migrations/` | RSpec | |
+| `app/policies/` | `spec/policies/` | RSpec | |
+| `app/presenters/` | `spec/presenters/` | RSpec | |
+| `app/routing/` | `spec/routing/` | RSpec | |
+| `app/serializers/` | `spec/serializers/` | RSpec | |
+| `app/services/` | `spec/services/` | RSpec | |
+| `app/tasks/` | `spec/tasks/` | RSpec | |
+| `app/uploaders/` | `spec/uploaders/` | RSpec | |
+| `app/views/` | `spec/views/` | RSpec | |
+| `app/workers/` | `spec/workers/` | RSpec | |
+| `app/assets/javascripts/` | `spec/javascripts/` | Karma | More details in the [JavaScript](#javascript) section. |
+
+### Integration tests
+
+Formal definition: https://en.wikipedia.org/wiki/Integration_testing
+
+These kind of tests ensure that individual parts of the application work well together, without the overhead of the actual app environment (i.e. the browser). These tests should assert at the request/response level: status code, headers, body. They're useful to test permissions, redirections, what view is rendered etc.
+
+| Code path | Tests path | Testing engine | Notes |
+| --------- | ---------- | -------------- | ----- |
+| `app/controllers/` | `spec/controllers/` | RSpec | |
+| `app/mailers/` | `spec/mailers/` | RSpec | |
+| `lib/api/` | `spec/requests/api/` | RSpec | |
+| `lib/ci/api/` | `spec/requests/ci/api/` | RSpec | |
+| `app/assets/javascripts/` | `spec/javascripts/` | Karma | More details in the [JavaScript](#javascript) section. |
+
+#### About controller tests
+
+In an ideal world, controllers should be thin. However, when this is not the
+case, it's acceptable to write a system/feature test without JavaScript instead
+of a controller test. The reason is that testing a fat controller usually
+involves a lot of stubbing, things like:
-GitLab uses [factory_girl] as a test fixture replacement.
-
-- Factory definitions live in `spec/factories/`, named using the pluralization
- of their corresponding model (`User` factories are defined in `users.rb`).
-- There should be only one top-level factory definition per file.
-- FactoryGirl methods are mixed in to all RSpec groups. This means you can (and
- should) call `create(...)` instead of `FactoryGirl.create(...)`.
-- Make use of [traits] to clean up definitions and usages.
-- When defining a factory, don't define attributes that are not required for the
- resulting record to pass validation.
-- When instantiating from a factory, don't supply attributes that aren't
- required by the test.
-- Factories don't have to be limited to `ActiveRecord` objects.
- [See example](https://gitlab.com/gitlab-org/gitlab-ce/commit/0b8cefd3b2385a21cfed779bd659978c0402766d).
-
-[factory_girl]: https://github.com/thoughtbot/factory_girl
-[traits]: http://www.rubydoc.info/gems/factory_girl/file/GETTING_STARTED.md#Traits
-
-## JavaScript
-
-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`
- has a corresponding `spec/javascripts/behaviors/autosize_spec.js` file.
-- Haml fixtures required for JavaScript tests live in
- `spec/javascripts/fixtures`. They should contain the bare minimum amount of
- markup necessary for the test.
-
- > **Warning:** Keep in mind that a Rails view may change and
- invalidate your test, but everything will still pass because your fixture
- doesn't reflect the latest view. Because of this we encourage you to
- generate fixtures from actual rails views whenever possible.
-
-- Keep in mind that in a CI environment, these tests are run in a headless
- browser and you will not have access to certain APIs, such as
- [`Notification`](https://developer.mozilla.org/en-US/docs/Web/API/notification),
- which will have to be stubbed.
-
-[Karma]: https://github.com/karma-runner/karma
-[Jasmine]: https://github.com/jasmine/jasmine
+```ruby
+controller.instance_variable_set(:@user, user)
+```
-For more information, see the [frontend testing guide](fe_guide/testing.md).
+and use methods which are deprecated in Rails 5 ([#23768]).
+
+[#23768]: https://gitlab.com/gitlab-org/gitlab-ce/issues/23768
+
+#### About Karma
+
+As you may have noticed, Karma is both in the Unit tests and the Integration
+tests category. That's because Karma is a tool that provides an environment to
+run JavaScript tests, so you can either run unit tests (e.g. test a single
+JavaScript method), or integration tests (e.g. test a component that is composed
+of multiple components).
+
+### System tests or Feature tests
+
+Formal definition: https://en.wikipedia.org/wiki/System_testing.
+
+These kind of tests ensure the application works as expected from a user point
+of view (aka black-box testing). These tests should test a happy path for a
+given page or set of pages, and a test case should be added for any regression
+that couldn't have been caught at lower levels with better tests (i.e. if a
+regression is found, regression tests should be added at the lowest-level
+possible).
+
+| Tests path | Testing engine | Notes |
+| ---------- | -------------- | ----- |
+| `spec/features/` | [Capybara] + [RSpec] | If your spec has the `:js` metadata, the browser driver will be [Poltergeist], otherwise it's using [RackTest]. |
+| `features/` | Spinach | Spinach tests are deprecated, [you shouldn't add new Spinach tests](#spinach-feature-tests). |
+
+[Capybara]: https://github.com/teamcapybara/capybara
+[RSpec]: https://github.com/rspec/rspec-rails#feature-specs
+[Poltergeist]: https://github.com/teamcapybara/capybara#poltergeist
+[RackTest]: https://github.com/teamcapybara/capybara#racktest
+
+#### Best practices
+
+- Create only the necessary records in the database
+- Test a happy path and a less happy path but that's it
+- Every other possible path should be tested with Unit or Integration tests
+- Test what's displayed on the page, not the internals of ActiveRecord models.
+ For instance, if you want to verify that a record was created, add
+ expectations that its attributes are displayed on the page, not that
+ `Model.count` increased by one.
+- It's ok to look for DOM elements but don't abuse it since it makes the tests
+ more brittle
+
+If we're confident that the low-level components work well (and we should be if
+we have enough Unit & Integration tests), we shouldn't need to duplicate their
+thorough testing at the System test level.
+
+It's very easy to add tests, but a lot harder to remove or improve tests, so one
+should take care of not introducing too many (slow and duplicated) specs.
+
+The reasons why we should follow these best practices are as follows:
+
+- System tests are slow to run since they spin up the entire application stack
+ in a headless browser, and even slower when they integrate a JS driver
+- When system tests run with a JavaScript driver, the tests are run in a
+ different thread than the application. This means it does not share a
+ database connection and your test will have to commit the transactions in
+ order for the running application to see the data (and vice-versa). In that
+ case we need to truncate the database after each spec instead of simply
+ rolling back a transaction (the faster strategy that's in use for other kind
+ of tests). This is slower than transactions, however, so we want to use
+ truncation only when necessary.
+
+### Black-box tests or End-to-end tests
+
+GitLab consists of [multiple pieces] such as [GitLab Shell], [GitLab Workhorse],
+[Gitaly], [GitLab Pages], [GitLab Runner], and GitLab Rails. All theses pieces
+are configured and packaged by [GitLab Omnibus].
+
+[GitLab QA] is a tool that allows to test that all these pieces integrate well
+together by building a Docker image for a given version of GitLab Rails and
+running feature tests (i.e. using Capybara) against it.
+
+The actual test scenarios and steps are [part of GitLab Rails] so that they're
+always in-sync with the codebase.
+
+[multiple pieces]: ./architecture.md#components
+[GitLab Shell]: https://gitlab.com/gitlab-org/gitlab-shell
+[GitLab Workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse
+[Gitaly]: https://gitlab.com/gitlab-org/gitaly
+[GitLab Pages]: https://gitlab.com/gitlab-org/gitlab-pages
+[GitLab Runner]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner
+[GitLab Omnibus]: https://gitlab.com/gitlab-org/omnibus-gitlab
+[GitLab QA]: https://gitlab.com/gitlab-org/gitlab-qa
+[part of GitLab Rails]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa
+
+## How to test at the correct level?
+
+As many things in life, deciding what to test at each level of testing is a
+trade-off:
+
+- Unit tests are usually cheap, and you should consider them like the basement
+ of your house: you need them to be confident that your code is behaving
+ correctly. However if you run only unit tests without integration / system tests, you might [miss] the [big] [picture]!
+- Integration tests are a bit more expensive, but don't abuse them. A feature test
+ is often better than an integration test that is stubbing a lot of internals.
+- System tests are expensive (compared to unit tests), even more if they require
+ a JavaScript driver. Make sure to follow the guidelines in the [Speed](#test-speed)
+ section.
+
+Another way to see it is to think about the "cost of tests", this is well
+explained [in this article][tests-cost] and the basic idea is that the cost of a
+test includes:
+
+- The time it takes to write the test
+- The time it takes to run the test every time the suite runs
+- The time it takes to understand the test
+- The time it takes to fix the test if it breaks and the underlying code is OK
+- Maybe, the time it takes to change the code to make the code testable.
+
+[miss]: https://twitter.com/ThePracticalDev/status/850748070698651649
+[big]: https://twitter.com/timbray/status/822470746773409794
+[picture]: https://twitter.com/withzombies/status/829716565834752000
+[tests-cost]: https://medium.com/table-xi/high-cost-tests-and-high-value-tests-a86e27a54df#.2ulyh3a4e
+
+## Frontend testing
+
+Please consult the [dedicated "Frontend testing" guide](./fe_guide/testing.md).
## RSpec
@@ -117,53 +244,124 @@ it 'is overdue' do
end
```
-### Test speed
+### System / Feature tests
-GitLab has a massive test suite that, without parallelization, can take more
-than an hour to run. It's important that we make an effort to write tests that
-are accurate and effective _as well as_ fast.
+- Feature specs should be named `ROLE_ACTION_spec.rb`, such as
+ `user_changes_password_spec.rb`.
+- Use only one `feature` block per feature spec file.
+- Use scenario titles that describe the success and failure paths.
+- Avoid scenario titles that add no information, such as "successfully".
+- Avoid scenario titles that repeat the feature title.
-Here are some things to keep in mind regarding test performance:
+### Matchers
-- `double` and `spy` are faster than `FactoryGirl.build(...)`
-- `FactoryGirl.build(...)` and `.build_stubbed` are faster than `.create`.
-- Don't `create` an object when `build`, `build_stubbed`, `attributes_for`,
- `spy`, or `double` will do. Database persistence is slow!
-- Use `create(:empty_project)` instead of `create(:project)` when you don't need
- the underlying Git repository. Filesystem operations are slow!
-- Don't mark a feature as requiring JavaScript (through `@javascript` in
- Spinach or `js: true` in RSpec) unless it's _actually_ required for the test
- to be valid. Headless browser testing is slow!
+Custom matchers should be created to clarify the intent and/or hide the
+complexity of RSpec expectations.They should be placed under
+`spec/support/matchers/`. Matchers can be placed in subfolder if they apply to
+a certain type of specs only (e.g. features, requests etc.) but shouldn't be if
+they apply to multiple type of specs.
-### Features / Integration
+### Shared contexts
-GitLab uses [rspec-rails feature specs] to test features in a browser
-environment. These are [capybara] specs running on the headless [poltergeist]
-driver.
+All shared contexts should be be placed under `spec/support/shared_contexts/`.
+Shared contexts can be placed in subfolder if they apply to a certain type of
+specs only (e.g. features, requests etc.) but shouldn't be if they apply to
+multiple type of specs.
-- 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.
-- Use scenario titles that describe the success and failure paths.
-- Avoid scenario titles that add no information, such as "successfully."
-- Avoid scenario titles that repeat the feature title.
+Each file should include only one context and have a descriptive name, e.g.
+`spec/support/shared_contexts/controllers/githubish_import_controller_shared_context.rb`.
-[rspec-rails feature specs]: https://github.com/rspec/rspec-rails#feature-specs
-[capybara]: https://github.com/teamcapybara/capybara
-[poltergeist]: https://github.com/teampoltergeist/poltergeist
+### Shared examples
-## Spinach (feature) tests
+All shared examples should be be placed under `spec/support/shared_examples/`.
+Shared examples can be placed in subfolder if they apply to a certain type of
+specs only (e.g. features, requests etc.) but shouldn't be if they apply to
+multiple type of specs.
-GitLab [moved from Cucumber to Spinach](https://github.com/gitlabhq/gitlabhq/pull/1426)
-for its feature/integration tests in September 2012.
+Each file should include only one context and have a descriptive name, e.g.
+`spec/support/shared_examples/controllers/githubish_import_controller_shared_example.rb`.
-As of March 2016, we are [trying to avoid adding new Spinach
-tests](https://gitlab.com/gitlab-org/gitlab-ce/issues/14121) going forward,
-opting for [RSpec feature](#features-integration) specs.
+### Helpers
-Adding new Spinach scenarios is acceptable _only if_ the new scenario requires
-no more than one new `step` definition. If more than that is required, the
-test should be re-implemented using RSpec instead.
+Helpers are usually modules that provide some methods to hide the complexity of
+specific RSpec examples. You can define helpers in RSpec files if they're not
+intended to be shared with other specs. Otherwise, they should be be placed
+under `spec/support/helpers/`. Helpers can be placed in subfolder if they apply
+to a certain type of specs only (e.g. features, requests etc.) but shouldn't be
+if they apply to multiple type of specs.
+
+Helpers should follow the Rails naming / namespacing convention. For instance
+`spec/support/helpers/cycle_analytics_helpers.rb` should define:
+
+```ruby
+module Spec
+ module Support
+ module Helpers
+ module CycleAnalyticsHelpers
+ def create_commit_referencing_issue(issue, branch_name: random_git_name)
+ project.repository.add_branch(user, branch_name, 'master')
+ create_commit("Commit for ##{issue.iid}", issue.project, user, branch_name)
+ end
+ end
+ end
+ end
+end
+```
+
+Helpers should not change the RSpec config. For instance, the helpers module
+described above should not include:
+
+```ruby
+RSpec.configure do |config|
+ config.include Spec::Support::Helpers::CycleAnalyticsHelpers
+end
+```
+
+### Factories
+
+GitLab uses [factory_girl] as a test fixture replacement.
+
+- Factory definitions live in `spec/factories/`, named using the pluralization
+ of their corresponding model (`User` factories are defined in `users.rb`).
+- There should be only one top-level factory definition per file.
+- FactoryGirl methods are mixed in to all RSpec groups. This means you can (and
+ should) call `create(...)` instead of `FactoryGirl.create(...)`.
+- Make use of [traits] to clean up definitions and usages.
+- When defining a factory, don't define attributes that are not required for the
+ resulting record to pass validation.
+- When instantiating from a factory, don't supply attributes that aren't
+ required by the test.
+- Factories don't have to be limited to `ActiveRecord` objects.
+ [See example](https://gitlab.com/gitlab-org/gitlab-ce/commit/0b8cefd3b2385a21cfed779bd659978c0402766d).
+
+[factory_girl]: https://github.com/thoughtbot/factory_girl
+[traits]: http://www.rubydoc.info/gems/factory_girl/file/GETTING_STARTED.md#Traits
+
+### Fixtures
+
+All fixtures should be be placed under `spec/fixtures/`.
+
+### Config
+
+RSpec config files are files that change the RSpec config (i.e.
+`RSpec.configure do |config|` blocks). They should be placed under
+`spec/support/config/`.
+
+Each file should be related to a specific domain, e.g.
+`spec/support/config/capybara.rb`, `spec/support/config/carrierwave.rb`, etc.
+
+Helpers can be included in the `spec/support/config/rspec.rb` file. If a
+helpers module applies only to a certain kind of specs, it should add modifiers
+to the `config.include` call. For instance if
+`spec/support/helpers/cycle_analytics_helpers.rb` applies to `:lib` and
+`type: :model` specs only, you would write the following:
+
+```ruby
+RSpec.configure do |config|
+ config.include Spec::Support::Helpers::CycleAnalyticsHelpers, :lib
+ config.include Spec::Support::Helpers::CycleAnalyticsHelpers, type: :model
+end
+```
## Testing Rake Tasks
@@ -201,6 +399,77 @@ describe 'gitlab:shell rake tasks' do
end
```
+## Test speed
+
+GitLab has a massive test suite that, without [parallelization], can take hours
+to run. It's important that we make an effort to write tests that are accurate
+and effective _as well as_ fast.
+
+Here are some things to keep in mind regarding test performance:
+
+- `double` and `spy` are faster than `FactoryGirl.build(...)`
+- `FactoryGirl.build(...)` and `.build_stubbed` are faster than `.create`.
+- Don't `create` an object when `build`, `build_stubbed`, `attributes_for`,
+ `spy`, or `double` will do. Database persistence is slow!
+- Use `create(:empty_project)` instead of `create(:project)` when you don't need
+ the underlying Git repository. Filesystem operations are slow!
+- Don't mark a feature as requiring JavaScript (through `@javascript` in
+ Spinach or `:js` in RSpec) unless it's _actually_ required for the test
+ to be valid. Headless browser testing is slow!
+
+[parallelization]: #test-suite-parallelization-on-the-ci
+
+### Test suite parallelization on the CI
+
+Our current CI parallelization setup is as follows:
+
+1. The `knapsack` job in the prepare stage that is supposed to ensure we have a
+ `knapsack/${CI_PROJECT_NAME}/rspec_report-master.json` file:
+ - The `knapsack/${CI_PROJECT_NAME}/rspec_report-master.json` file is fetched
+ from S3, if it's not here we initialize the file with `{}`.
+1. Each `rspec x y` job are run with `knapsack rspec` and should have an evenly
+ distributed share of tests:
+ - It works because the jobs have access to the
+ `knapsack/${CI_PROJECT_NAME}/rspec_report-master.json` since the "artifacts
+ from all previous stages are passed by default". [^1]
+ - the jobs set their own report path to
+ `KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json`.
+ - if knapsack is doing its job, test files that are run should be listed under
+ `Report specs`, not under `Leftover specs`.
+1. The `update-knapsack` job takes all the
+ `knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json`
+ files from the `rspec x y` jobs and merge them all together into a single
+ `knapsack/${CI_PROJECT_NAME}/rspec_report-master.json` file that is then
+ uploaded to S3.
+
+After that, the next pipeline will use the up-to-date
+`knapsack/${CI_PROJECT_NAME}/rspec_report-master.json` file. The same strategy
+is used for Spinach tests as well.
+
+### Monitoring
+
+The GitLab test suite is [monitored] and a [public dashboard] is available for
+everyone to see. Feel free to look at the slowest test files and try to improve
+them.
+
+[monitored]: ./performance.md#rspec-profiling
+[public dashboard]: https://redash.gitlab.com/public/dashboards/l1WhHXaxrCWM5Ai9D7YDqHKehq6OU3bx5gssaiWe?org_slug=default
+
+## Spinach (feature) tests
+
+GitLab [moved from Cucumber to Spinach](https://github.com/gitlabhq/gitlabhq/pull/1426)
+for its feature/integration tests in September 2012.
+
+As of March 2016, we are [trying to avoid adding new Spinach
+tests](https://gitlab.com/gitlab-org/gitlab-ce/issues/14121) going forward,
+opting for [RSpec feature](#features-integration) specs.
+
+Adding new Spinach scenarios is acceptable _only if_ the new scenario requires
+no more than one new `step` definition. If more than that is required, the
+test should be re-implemented using RSpec instead.
+
---
[Return to Development documentation](README.md)
+
+[^1]: /ci/yaml/README.html#dependencies
diff --git a/doc/development/what_requires_downtime.md b/doc/development/what_requires_downtime.md
index bbcd26477f3..8da6ad684f5 100644
--- a/doc/development/what_requires_downtime.md
+++ b/doc/development/what_requires_downtime.md
@@ -2,7 +2,8 @@
When working with a database certain operations can be performed without taking
GitLab offline, others do require a downtime period. This guide describes
-various operations and their impact.
+various operations, their impact, and how to perform them without requiring
+downtime.
## Adding Columns
@@ -41,50 +42,156 @@ information on how to use this method.
## Dropping Columns
-On PostgreSQL you can safely remove an existing column without the need for
-downtime. When you drop a column in PostgreSQL it's not immediately removed,
-instead it is simply disabled. The data is removed on the next vacuum run.
+Removing columns is tricky because running GitLab processes may still be using
+the columns. To work around this you will need two separate merge requests and
+releases: one to ignore and then remove the column, and one to remove the ignore
+rule.
-On MySQL this operation requires downtime.
+### Step 1: Ignoring The Column
-While database wise dropping a column may be fine on PostgreSQL this operation
-still requires downtime because the application code may still be using the
-column that was removed. For example, consider the following migration:
+The first step is to ignore the column in the application code. This is
+necessary because Rails caches the columns and re-uses this cache in various
+places. This can be done by including the `IgnorableColumn` module into the
+model, followed by defining the columns to ignore. For example, to ignore
+`updated_at` in the User model you'd use the following:
```ruby
-class MyMigration < ActiveRecord::Migration
- def change
- remove_column :projects, :dummy
- end
+class User < ActiveRecord::Base
+ include IgnorableColumn
+
+ ignore_column :updated_at
end
```
-Now imagine that the GitLab instance is running and actively uses the `dummy`
-column. If we were to run the migration this would result in the GitLab instance
-producing errors whenever it tries to use the `dummy` column.
+Once added you should create a _post-deployment_ migration that removes the
+column. Both these changes should be submitted in the same merge request.
-As a result of the above downtime _is_ required when removing a column, even
-when using PostgreSQL.
+### Step 2: Removing The Ignore Rule
+
+Once the changes from step 1 have been released & deployed you can set up a
+separate merge request that removes the ignore rule. This merge request can
+simply remove the `ignore_column` line, and the `include IgnorableColumn` line
+if no other `ignore_column` calls remain.
## Renaming Columns
-Renaming columns requires downtime as running GitLab instances will continue
-using the old column name until a new version is deployed. This can result
-in the instance producing errors, which in turn can impact the user experience.
+Renaming columns the normal way requires downtime as an application may continue
+using the old column name during/after a database migration. To rename a column
+without requiring downtime we need two migrations: a regular migration, and a
+post-deployment migration. Both these migration can go in the same release.
-## Changing Column Constraints
+### Step 1: Add The Regular Migration
+
+First we need to create the regular migration. This migration should use
+`Gitlab::Database::MigrationHelpers#rename_column_concurrently` to perform the
+renaming. For example
+
+```ruby
+# A regular migration in db/migrate
+class RenameUsersUpdatedAtToUpdatedAtTimestamp < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ disable_ddl_transaction!
+
+ def up
+ rename_column_concurrently :users, :updated_at, :updated_at_timestamp
+ end
+
+ def down
+ cleanup_concurrent_column_rename :users, :updated_at_timestamp, :updated_at
+ end
+end
+```
+
+This will take care of renaming the column, ensuring data stays in sync, copying
+over indexes and foreign keys, etc.
+
+**NOTE:** if a column contains 1 or more indexes that do not contain the name of
+the original column, the above procedure will fail. In this case you will first
+need to rename these indexes.
-Generally changing column constraints requires checking all rows in the table to
-see if they meet the new constraint, unless a constraint is _removed_. For
-example, changing a column that previously allowed NULL values to not allow NULL
-values requires the database to verify all existing rows.
+### Step 2: Add A Post-Deployment Migration
-The specific behaviour varies a bit between databases but in general the safest
-approach is to assume changing constraints requires downtime.
+The renaming procedure requires some cleaning up in a post-deployment migration.
+We can perform this cleanup using
+`Gitlab::Database::MigrationHelpers#cleanup_concurrent_column_rename`:
+
+```ruby
+# A post-deployment migration in db/post_migrate
+class CleanupUsersUpdatedAtRename < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ disable_ddl_transaction!
+
+ def up
+ cleanup_concurrent_column_rename :users, :updated_at, :updated_at_timestamp
+ end
+
+ def down
+ rename_column_concurrently :users, :updated_at_timestamp, :updated_at
+ end
+end
+```
+
+## Changing Column Constraints
+
+Adding or removing a NOT NULL clause (or another constraint) can typically be
+done without requiring downtime. However, this does require that any application
+changes are deployed _first_. Thus, changing the constraints of a column should
+happen in a post-deployment migration.
## Changing Column Types
-This operation requires downtime.
+Changing the type of a column can be done using
+`Gitlab::Database::MigrationHelpers#change_column_type_concurrently`. This
+method works similarly to `rename_column_concurrently`. For example, let's say
+we want to change the type of `users.username` from `string` to `text`.
+
+### Step 1: Create A Regular Migration
+
+A regular migration is used to create a new column with a temporary name along
+with setting up some triggers to keep data in sync. Such a migration would look
+as follows:
+
+```ruby
+# A regular migration in db/migrate
+class ChangeUsersUsernameStringToText < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ disable_ddl_transaction!
+
+ def up
+ change_column_type_concurrently :users, :username, :text
+ end
+
+ def down
+ cleanup_concurrent_column_type_change :users, :username
+ end
+end
+```
+
+### Step 2: Create A Post Deployment Migration
+
+Next we need to clean up our changes using a post-deployment migration:
+
+```ruby
+# A post-deployment migration in db/post_migrate
+class ChangeUsersUsernameStringToTextCleanup < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ disable_ddl_transaction!
+
+ def up
+ cleanup_concurrent_column_type_change :users
+ end
+
+ def down
+ change_column_type_concurrently :users, :username, :string
+ end
+end
+```
+
+And that's it, we're done!
## Adding Indexes
@@ -101,12 +208,19 @@ Migrations can take advantage of this by using the method
```ruby
class MyMigration < ActiveRecord::Migration
- def change
+ def up
add_concurrent_index :projects, :column_name
end
+
+ def down
+ remove_index(:projects, :column_name) if index_exists?(:projects, :column_name)
+ end
end
```
+Note that `add_concurrent_index` can not be reversed automatically, thus you
+need to manually define `up` and `down`.
+
When running this on PostgreSQL the `CONCURRENTLY` option mentioned above is
used. On MySQL this method produces a regular `CREATE INDEX` query.
@@ -125,43 +239,54 @@ This operation is safe as there's no code using the table just yet.
## Dropping Tables
-This operation requires downtime as application code may still be using the
-table.
+Dropping tables can be done safely using a post-deployment migration, but only
+if the application no longer uses the table.
## Adding Foreign Keys
-Adding foreign keys acquires an exclusive lock on both the source and target
-tables in PostgreSQL. This requires downtime as otherwise the entire application
-grinds to a halt for the duration of the operation.
+Adding foreign keys usually works in 3 steps:
+
+1. Start a transaction
+1. Run `ALTER TABLE` to add the constraint(s)
+1. Check all existing data
-On MySQL this operation also requires downtime _unless_ foreign key checks are
-disabled. Because this means checks aren't enforced this is not ideal, as such
-one should assume MySQL also requires downtime.
+Because `ALTER TABLE` typically acquires an exclusive lock until the end of a
+transaction this means this approach would require downtime.
+
+GitLab allows you to work around this by using
+`Gitlab::Database::MigrationHelpers#add_concurrent_foreign_key`. This method
+ensures that when PostgreSQL is used no downtime is needed.
## Removing Foreign Keys
-This operation should not require downtime on both PostgreSQL and MySQL.
+This operation does not require downtime.
-## Updating Data
+## Data Migrations
-Updating data should generally be safe. The exception to this is data that's
-being migrated from one version to another while the application still produces
-data in the old version.
+Data migrations can be tricky. The usual approach to migrate data is to take a 3
+step approach:
-For example, imagine the application writes the string `'dog'` to a column but
-it really is meant to write `'cat'` instead. One might think that the following
-migration is all that is needed to solve this problem:
+1. Migrate the initial batch of data
+1. Deploy the application code
+1. Migrate any remaining data
-```ruby
-class MyMigration < ActiveRecord::Migration
- def up
- execute("UPDATE some_table SET column = 'cat' WHERE column = 'dog';")
- end
-end
-```
+Usually this works, but not always. For example, if a field's format is to be
+changed from JSON to something else we have a bit of a problem. If we were to
+change existing data before deploying application code we'll most likely run
+into errors. On the other hand, if we were to migrate after deploying the
+application code we could run into the same problems.
+
+If you merely need to correct some invalid data, then a post-deployment
+migration is usually enough. If you need to change the format of data (e.g. from
+JSON to something else) it's typically best to add a new column for the new data
+format, and have the application use that. In such a case the procedure would
+be:
-Unfortunately this is not enough. Because the application is still running and
-using the old value this may result in the table still containing rows where
-`column` is set to `dog`, even after the migration finished.
+1. Add a new column in the new format
+1. Copy over existing data to this new column
+1. Deploy the application code
+1. In a post-deployment migration, copy over any remaining data
-In these cases downtime _is_ required, even for rarely updated tables.
+In general there is no one-size-fits-all solution, therefore it's best to
+discuss these kind of migrations in a merge request to make sure they are
+implemented in the best way possible.
diff --git a/doc/gitlab-basics/create-group.md b/doc/gitlab-basics/create-group.md
index 64274ccd5eb..b4889bb8818 100644
--- a/doc/gitlab-basics/create-group.md
+++ b/doc/gitlab-basics/create-group.md
@@ -25,6 +25,8 @@ To create a group:
1. Set the "Group path" which will be the namespace under which your projects
will be hosted (path can contain only letters, digits, underscores, dashes
and dots; it cannot start with dashes or end in dot).
+ 1. The "Group name" will populate with the path. Optionally, you can change
+ it. This is the name that will display in the group views.
1. Optionally, you can add a description so that others can briefly understand
what this group is about.
1. Optionally, choose and avatar for your project.
diff --git a/doc/gitlab-basics/img/create_new_group_info.png b/doc/gitlab-basics/img/create_new_group_info.png
index 020b4ac00d6..8d2501d9f7a 100644
--- a/doc/gitlab-basics/img/create_new_group_info.png
+++ b/doc/gitlab-basics/img/create_new_group_info.png
Binary files differ
diff --git a/doc/install/README.md b/doc/install/README.md
index d35709266e4..58cc7d312fd 100644
--- a/doc/install/README.md
+++ b/doc/install/README.md
@@ -20,8 +20,8 @@ the hardware requirements.
- [Docker](https://docs.gitlab.com/omnibus/docker/) - Install GitLab using Docker.
- [Installation on Google Cloud Platform](google_cloud_platform/index.md) - Install
GitLab on Google Cloud Platform using our official image.
-- [Digital Ocean and Docker](digitaloceandocker.md) - Install GitLab quickly
- on DigitalOcean using Docker.
+- Testing only! [DigitalOcean and Docker Machine](digitaloceandocker.md) -
+ Quickly test any version of GitLab on DigitalOcean using Docker Machine.
## Database
diff --git a/doc/install/digitaloceandocker.md b/doc/install/digitaloceandocker.md
index 820060a489b..8efc0530b8a 100644
--- a/doc/install/digitaloceandocker.md
+++ b/doc/install/digitaloceandocker.md
@@ -1,4 +1,7 @@
-# Digital Ocean and Docker
+# Digital Ocean and Docker Machine test environment
+
+## Warning. This guide is for quickly testing different versions of GitLab and
+## not recommended for ease of future upgrades or keeping the data you create.
## Initial setup
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 5b72c2cce07..1f61a4f67bb 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -289,9 +289,9 @@ sudo usermod -aG redis git
### Clone the Source
# Clone GitLab repository
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 9-0-stable gitlab
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 9-1-stable gitlab
-**Note:** You can change `9-0-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
+**Note:** You can change `9-1-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It
@@ -475,7 +475,7 @@ with setting up Gitaly until you upgrade to GitLab 9.2 or later.
sudo -u git cp config.toml.example config.toml
# If you are using non-default settings you need to update config.toml
sudo -u git -H editor config.toml
-
+
# Enable Gitaly in the init script
echo 'gitaly_enabled=true' | sudo tee -a /etc/default/gitlab
diff --git a/doc/topics/authentication/index.md b/doc/topics/authentication/index.md
new file mode 100644
index 00000000000..eafd2fd9d04
--- /dev/null
+++ b/doc/topics/authentication/index.md
@@ -0,0 +1,46 @@
+# Authentication
+
+This page gathers all the resources for the topic **Authentication** within GitLab.
+
+## GitLab users
+
+- [SSH](../../ssh/README.md)
+- [Two-Factor Authentication (2FA)](../../user/profile/account/two_factor_authentication.md#two-factor-authentication)
+- **Articles:**
+ - [Support for Universal 2nd Factor Authentication - YubiKeys](https://about.gitlab.com/2016/06/22/gitlab-adds-support-for-u2f/)
+ - [Security Webcast with Yubico](https://about.gitlab.com/2016/08/31/gitlab-and-yubico-security-webcast/)
+- **Integrations:**
+ - [GitLab as OAuth2 authentication service provider](../../integration/oauth_provider.md#introduction-to-oauth)
+
+## GitLab administrators
+
+- [LDAP (Community Edition)](../../administration/auth/ldap.md)
+- [LDAP (Enterprise Edition)](https://docs.gitlab.com/ee/administration/auth/ldap-ee.html)
+- [Enforce Two-factor Authentication (2FA)](../../security/two_factor_authentication.md#enforce-two-factor-authentication-2fa)
+- **Articles:**
+ - [Feature Highlight: LDAP Integration](https://about.gitlab.com/2014/07/10/feature-highlight-ldap-sync/)
+ - [Debugging LDAP](https://about.gitlab.com/handbook/support/workflows/ldap/debugging_ldap.html)
+- **Integrations:**
+ - [OmniAuth](../../integration/omniauth.md)
+ - [Authentiq OmniAuth Provider](../../administration/auth/authentiq.md#authentiq-omniauth-provider)
+ - [Atlassian Crowd OmniAuth Provider](../../administration/auth/crowd.md)
+ - [CAS OmniAuth Provider](../../integration/cas.md)
+ - [SAML OmniAuth Provider](../../integration/saml.md)
+ - [Okta SSO provider](../../administration/auth/okta.md)
+ - [Kerberos integration (GitLab EE)](https://docs.gitlab.com/ee/integration/kerberos.html)
+
+## API
+
+- [OAuth 2 Tokens](../../api/README.md#oauth-2-tokens)
+- [Private Tokens](../../api/README.md#private-tokens)
+- [Impersonation tokens](../../api/README.md#impersonation-tokens)
+- [GitLab as an OAuth2 provider](../../api/oauth2.md#gitlab-as-an-oauth2-provider)
+- [GitLab Runner API - Authentication](../../api/ci/runners.md#authentication)
+
+## Third-party resources
+
+- [Kanboard Plugin GitLab Authentication](https://kanboard.net/plugin/gitlab-auth)
+- [Jenkins GitLab OAuth Plugin](https://wiki.jenkins-ci.org/display/JENKINS/GitLab+OAuth+Plugin)
+- [Setup Gitlab CE with Active Directory authentication](https://www.caseylabs.com/setup-gitlab-ce-with-active-directory-authentication/)
+- [How to customize GitLab to support OpenID authentication](http://eric.van-der-vlist.com/blog/2013/11/23/how-to-customize-gitlab-to-support-openid-authentication/)
+- [Openshift - Configuring Authentication and User Agent](https://docs.openshift.org/latest/install_config/configuring_authentication.html#GitLab)
diff --git a/doc/topics/git/index.md b/doc/topics/git/index.md
new file mode 100644
index 00000000000..b99ba317a43
--- /dev/null
+++ b/doc/topics/git/index.md
@@ -0,0 +1,61 @@
+# Git documentation
+
+Git is a [free and open source](https://git-scm.com/about/free-and-open-source)
+distributed version control system designed to handle everything from small to
+very large projects with speed and efficiency.
+
+[GitLab](https://about.gitlab.com) is a Git-based fully integrated platform for
+software development. Besides Git's functionalities, GitLab has a lot of
+powerful [features](https://about.gitlab.com/features/) to enhance your
+[workflow](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/).
+
+We've gathered some resources to help you to get the best from Git with GitLab.
+
+## Getting started
+
+- [Git concepts](../../university/training/user_training.md#git-concepts)
+- [Start using Git on the command line](../../gitlab-basics/start-using-git.md)
+- [Command Line basic commands](../../gitlab-basics/command-line-commands.md)
+- [GitLab Git Cheat Sheet (download)](https://gitlab.com/gitlab-com/marketing/raw/master/design/print/git-cheatsheet/print-pdf/git-cheatsheet.pdf)
+- **Articles:**
+ - [Git Tips & Tricks](https://about.gitlab.com/2016/12/08/git-tips-and-tricks/)
+ - [Eight Tips to help you work better with Git](https://about.gitlab.com/2015/02/19/8-tips-to-help-you-work-better-with-git/)
+- **Presentations:**
+ - [GLU Course: About Version Control](https://docs.google.com/presentation/d/16sX7hUrCZyOFbpvnrAFrg6tVO5_yT98IgdAqOmXwBho/edit?usp=sharing)
+- **Third-party resources:**
+ - What is [Git](https://git-scm.com)
+ - [Version control](https://git-scm.com/book/en/v2/Getting-Started-About-Version-Control)
+ - [Getting Started - Git Basics](https://git-scm.com/book/en/v2/Getting-Started-Git-Basics)
+ - [Getting Started - Installing Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
+ - [Git on the Server - GitLab](https://git-scm.com/book/en/v2/Git-on-the-Server-GitLab)
+
+## Branching strategies
+
+- **Articles:**
+ - [GitLab Flow](https://about.gitlab.com/2014/09/29/gitlab-flow/)
+- **Third-party resources:**
+ - [Git Branching - Branches in a Nutshell](https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell)
+ - [Git Branching - Branching Workflows](https://git-scm.com/book/en/v2/Git-Branching-Branching-Workflows)
+
+## Advanced use
+
+- [Custom Git Hooks](../../administration/custom_hooks.md)
+- [Git Attributes](../../user/project/git_attributes.md)
+- Git Submodules: [Using Git submodules with GitLab CI](../../ci/git_submodules.md#using-git-submodules-with-gitlab-ci)
+
+## API
+
+- [Gitignore templates](../../api/templates/gitignores.md)
+
+## Git LFS
+
+- [Git LFS](../../workflow/lfs/manage_large_binaries_with_git_lfs.md)
+- [Git-Annex to Git-LFS migration guide](https://docs.gitlab.com/ee/workflow/lfs/migrate_from_git_annex_to_git_lfs.html)
+- **Articles:**
+ - [Getting Started with Git LFS](https://about.gitlab.com/2017/01/30/getting-started-with-git-lfs-tutorial/)
+ - [Towards a production quality open source Git LFS server](https://about.gitlab.com/2015/08/13/towards-a-production-quality-open-source-git-lfs-server/)
+
+## General information
+
+- **Articles:**
+ - [The future of SaaS hosted Git repository pricing](https://about.gitlab.com/2016/05/11/git-repository-pricing/)
diff --git a/doc/topics/index.md b/doc/topics/index.md
index 6de13d79554..ad388dff822 100644
--- a/doc/topics/index.md
+++ b/doc/topics/index.md
@@ -7,10 +7,10 @@ you through better understanding GitLab's concepts
through our regular docs, and, when available, through articles (guides,
tutorials, technical overviews, blog posts) and videos.
-- [GitLab Installation](../install/README.md)
+- [Authentication](authentication/index.md)
- [Continuous Integration (GitLab CI)](../ci/README.md)
+- [Git](git/index.md)
+- [GitLab Installation](../install/README.md)
- [GitLab Pages](../user/project/pages/index.md)
->**Note:**
-Non-linked topics are currently under development and subjected to change.
-More topics will be available soon.
+>**Note:** More topics will be available soon.
diff --git a/doc/update/9.0-to-9.1.md b/doc/update/9.0-to-9.1.md
index ae983dea384..1191662ee14 100644
--- a/doc/update/9.0-to-9.1.md
+++ b/doc/update/9.0-to-9.1.md
@@ -1,9 +1,5 @@
# From 9.0 to 9.1
-** TODO: **
-
-# TODO clean out 9.0-specific stuff
-
Make sure you view this update guide from the tag (version) of GitLab you would
like to install. In most cases this should be the highest numbered production
tag (without rc in it). You can select the tag in the version dropdown at the
diff --git a/doc/update/README.md b/doc/update/README.md
index 837b31abb97..d024a809f24 100644
--- a/doc/update/README.md
+++ b/doc/update/README.md
@@ -48,6 +48,20 @@ GitLab provides official Docker images for both Community and Enterprise
editions. They are based on the Omnibus package and instructions on how to
update them are in [a separate document][omnidocker].
+## Upgrading without downtime
+
+Starting with GitLab 9.1.0 it's possible to upgrade to a newer major, minor, or patch version of GitLab
+without having to take your GitLab instance offline. However, for this to work
+there are the following requirements:
+
+1. You can only upgrade 1 minor release at a time. So from 9.1 to 9.2, not to 9.3.
+2. You have to be on the most recent patch release. For example, if 9.1.15 is the last
+ release of 9.1 then you can safely upgrade from that version to any 9.2.x version.
+ However, if you are running 9.1.14 you first need to upgrade to 9.1.15.
+2. You have to use [post-deployment
+ migrations](../development/post_deployment_migrations.md).
+3. You are using PostgreSQL. If you are using MySQL please look at the release post to see if downtime is required.
+
## Upgrading between editions
GitLab comes in two flavors: [Community Edition][ce] which is MIT licensed,
diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md
index 1c493599cf8..f69d567eeb7 100644
--- a/doc/update/patch_versions.md
+++ b/doc/update/patch_versions.md
@@ -57,7 +57,7 @@ sudo -u git -H bundle clean
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
# Clean up assets and cache
-sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile cache:clear RAILS_ENV=production
+sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile cache:clear RAILS_ENV=production NODE_ENV=production
```
### 4. Update gitlab-workhorse to the corresponding version
diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md
index 63a3d3c472e..fb69d934ae1 100644
--- a/doc/user/profile/account/two_factor_authentication.md
+++ b/doc/user/profile/account/two_factor_authentication.md
@@ -143,7 +143,7 @@ into the password field.
To disable two-factor authentication on your account (for example, if you
have lost your code generation device) you can:
* [Use a saved recovery code](#use-a-saved-recovery-code)
-* [Generate new recovery codes using SSH](#generate-new-recovery-codes-using-SSH)
+* [Generate new recovery codes using SSH](#generate-new-recovery-codes-using-ssh)
* [Ask a GitLab administrator to disable two-factor authentication on your account](#ask-a-gitlab-administrator-to-disable-two-factor-authentication-on-your-account)
### Use a saved recovery code
diff --git a/doc/user/project/cycle_analytics.md b/doc/user/project/cycle_analytics.md
index 62afd8cf247..8f6b530c033 100644
--- a/doc/user/project/cycle_analytics.md
+++ b/doc/user/project/cycle_analytics.md
@@ -5,10 +5,10 @@
Cycle Analytics measures the time it takes to go from an [idea to production] for
each project you have. This is achieved by not only indicating the total time it
-takes to reach at that point, but the total time is broken down into the
+takes to reach that point, but the total time is broken down into the
multiple stages an idea has to pass through to be shipped.
-Cycle Analytics is that it is tightly coupled with the [GitLab flow] and
+Cycle Analytics is tightly coupled with the [GitLab flow] and
calculates a separate median for each stage.
## Overview
diff --git a/doc/user/project/integrations/kubernetes.md b/doc/user/project/integrations/kubernetes.md
index 2a890acde4d..f7d5e3a8ab2 100644
--- a/doc/user/project/integrations/kubernetes.md
+++ b/doc/user/project/integrations/kubernetes.md
@@ -60,7 +60,7 @@ to use terminals. Support is currently limited to the first container in the
first pod of your environment.
When enabled, the Kubernetes service adds [web terminal](../../../ci/environments.md#web-terminals)
-support to your environments. This is based on the `exec` functionality found in
+support to your [environments](../../../ci/environments.md). This is based on the `exec` functionality found in
Docker and Kubernetes, so you get a new shell session within your existing
containers. To use this integration, you should deploy to Kubernetes using
the deployment variables above, ensuring any pods you create are labelled with
diff --git a/doc/user/search/img/search_history.gif b/doc/user/search/img/search_history.gif
new file mode 100644
index 00000000000..4cfa48ee0ab
--- /dev/null
+++ b/doc/user/search/img/search_history.gif
Binary files differ
diff --git a/doc/user/search/index.md b/doc/user/search/index.md
index 9d1ca1adcb2..45f443819ec 100644
--- a/doc/user/search/index.md
+++ b/doc/user/search/index.md
@@ -40,6 +40,12 @@ The same process is valid for merge requests. Navigate to your project's **Merge
and click **Search or filter results...**. Merge requests can be filtered by author, assignee,
milestone, and label.
+## Search History
+
+You can view recent searches by clicking on the little arrow-clock icon, which is to the left of the search input. Click the search entry to run that search again. This feature is available for issues and merge requests. Searches are stored locally in your browser.
+
+![search history](img/search_history.gif)
+
### Shortcut
You'll also find a shortcut on the search field on the top-right of the project's dashboard to
diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md
index d12c0c6d0c4..1b172b21f3d 100644
--- a/doc/workflow/gitlab_flow.md
+++ b/doc/workflow/gitlab_flow.md
@@ -313,5 +313,4 @@ Merging only when needed prevents creating merge commits in your feature branch
### References
-- [Sketch file](https://www.dropbox.com/s/58dvsj5votbwrzv/git_flows.sketch?dl=0) with vectors of images in this article
- [Git Flow by Vincent Driessen](http://nvie.com/posts/a-successful-git-branching-model/)
diff --git a/doc/workflow/groups.md b/doc/workflow/groups.md
index 6237a5d5e18..882747e14e9 100644
--- a/doc/workflow/groups.md
+++ b/doc/workflow/groups.md
@@ -11,9 +11,9 @@ You can create a group by going to the 'Groups' tab of the GitLab dashboard and
![Click the 'New group' button in the 'Groups' tab](groups/new_group_button.png)
-Next, enter the name (required) and the optional description and group avatar.
+Next, enter the path and name (required) and the optional description and group avatar.
-![Fill in the name for your new group](groups/new_group_form.png)
+![Fill in the path for your new group](groups/new_group_form.png)
When your group has been created you are presented with the group dashboard feed, which will be empty.
diff --git a/doc/workflow/groups/new_group_form.png b/doc/workflow/groups/new_group_form.png
index 0d798cd4b84..91727ab5336 100644
--- a/doc/workflow/groups/new_group_form.png
+++ b/doc/workflow/groups/new_group_form.png
Binary files differ
diff --git a/doc/workflow/importing/import_projects_from_github.md b/doc/workflow/importing/import_projects_from_github.md
index aece4ab34ba..8ed1d98d05b 100644
--- a/doc/workflow/importing/import_projects_from_github.md
+++ b/doc/workflow/importing/import_projects_from_github.md
@@ -10,6 +10,11 @@ in your GitLab instance sitewide. This configuration is optional, users will
still be able to import their GitHub repositories with a
[personal access token][gh-token].
+>**Note:**
+Administrators of a GitLab instance (Community or Enterprise Edition) can also
+use the [GitHub rake task][gh-rake] to import projects from GitHub without the
+constrains of a Sidekiq worker.
+
- At its current state, GitHub importer can import:
- the repository description (GitLab 7.7+)
- the Git repository data (GitLab 7.7+)
@@ -112,5 +117,6 @@ You can also choose a different name for the project and a different namespace,
if you have the privileges to do so.
[gh-import]: ../../integration/github.md "GitHub integration"
+[gh-rake]: ../../administration/raketasks/github_import.md "GitHub rake task"
[gh-integration]: #authorize-access-to-your-repositories-using-the-github-integration
[gh-token]: #authorize-access-to-your-repositories-using-a-personal-access-token
diff --git a/doc/workflow/shortcuts.md b/doc/workflow/shortcuts.md
index f94357abec9..c5b7488be69 100644
--- a/doc/workflow/shortcuts.md
+++ b/doc/workflow/shortcuts.md
@@ -75,3 +75,9 @@ You can see GitLab's keyboard shortcuts by using 'shift + ?'
| <kbd>r</kbd> | Reply (quoting selected text) |
| <kbd>e</kbd> | Edit issue/merge request |
| <kbd>l</kbd> | Change label |
+
+## Wiki pages
+
+| Keyboard Shortcut | Description |
+| ----------------- | ----------- |
+| <kbd>e</kbd> | Edit wiki page|
diff --git a/doc/workflow/todos.md b/doc/workflow/todos.md
index 4b0fba842e9..3d8d3ce8f13 100644
--- a/doc/workflow/todos.md
+++ b/doc/workflow/todos.md
@@ -111,7 +111,7 @@ There are four kinds of filters you can use on your Todos dashboard.
| Type | Filter by issue or merge request |
| Action | Filter by the action that triggered the Todo |
-You can also filter by more than one of these at the same time.
+You can also filter by more than one of these at the same time. The possible Actions are `Any Action`, `Assigned`, `Mentioned`, `Added`, `Pipelines`, and `Directly Addressed`, [as described above](#what-triggers-a-todo).
[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/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb
index a4cfc1fb8c8..dfd0bc13305 100644
--- a/features/steps/project/issues/award_emoji.rb
+++ b/features/steps/project/issues/award_emoji.rb
@@ -87,7 +87,7 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
end
step 'I search "hand"' do
- fill_in 'emoji_search', with: 'hand'
+ fill_in 'emoji-menu-search', with: 'hand'
end
step 'I see search result for "hand"' do
@@ -101,7 +101,7 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
end
step 'The search field is focused' do
- expect(page).to have_selector('#emoji_search')
- expect(page.evaluate_script('document.activeElement.id')).to eq('emoji_search')
+ expect(page).to have_selector('.js-emoji-menu-search')
+ expect(page.evaluate_script("document.activeElement.classList.contains('js-emoji-menu-search')")).to eq(true)
end
end
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
index 975c879149e..280d70925f7 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -66,12 +66,6 @@ class Spinach::Features::Project < Spinach::FeatureSteps
expect(page).not_to have_link('Remove avatar')
end
- step 'I should see project "Shop" version' do
- page.within '.project-side' do
- expect(page).to have_content '6.7.0.pre'
- end
- end
-
step 'change project default branch' do
select 'fix', from: 'project_default_branch'
click_button 'Save changes'
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 45625e00f7d..9919762cd82 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -14,7 +14,7 @@ module API
class User < UserBasic
expose :created_at
- expose :is_admin?, as: :is_admin
+ expose :admin?, as: :is_admin
expose :bio, :location, :skype, :linkedin, :twitter, :website_url, :organization
end
@@ -611,9 +611,9 @@ module API
expose :locked
expose :version, :revision, :platform, :architecture
expose :contacted_at
- expose :token, if: lambda { |runner, options| options[:current_user].is_admin? || !runner.is_shared? }
+ expose :token, if: lambda { |runner, options| options[:current_user].admin? || !runner.is_shared? }
expose :projects, with: Entities::BasicProjectDetails do |runner, options|
- if options[:current_user].is_admin?
+ if options[:current_user].admin?
runner.projects
else
options[:current_user].authorized_projects.where(id: runner.projects)
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 605769eddde..09d105f6b4c 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -5,11 +5,16 @@ module API
before { authenticate! }
helpers do
- params :optional_params do
+ params :optional_params_ce do
optional :description, type: String, desc: 'The description of the group'
optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the group'
optional :lfs_enabled, type: Boolean, desc: 'Enable/disable LFS for the projects in this group'
optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access'
+ optional :share_with_group_lock, type: Boolean, desc: 'Prevent sharing a project with another group within this group'
+ end
+
+ params :optional_params do
+ use :optional_params_ce
end
params :statistics_params do
@@ -56,7 +61,7 @@ module API
groups = groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present?
groups = groups.reorder(params[:order_by] => params[:sort])
- present_groups groups, statistics: params[:statistics] && current_user.is_admin?
+ present_groups groups, statistics: params[:statistics] && current_user.admin?
end
desc 'Create a group. Available only for users who can create groups.' do
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 61527c1e20b..ddff3c8c1e8 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -118,7 +118,7 @@ module API
def authenticated_as_admin!
authenticate!
- forbidden! unless current_user.is_admin?
+ forbidden! unless current_user.admin?
end
def authorize!(action, subject = :global)
@@ -358,7 +358,7 @@ module API
return unless sudo_identifier
return unless initial_current_user
- unless initial_current_user.is_admin?
+ unless initial_current_user.admin?
forbidden!('Must be admin to use sudo')
end
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index 2135a787b11..810e5063996 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -53,12 +53,12 @@ module API
]
end
- def parse_allowed_environment_variables
- return if params[:env].blank?
+ def parse_env
+ return {} if params[:env].blank?
JSON.parse(params[:env])
-
rescue JSON::ParserError
+ {}
end
end
end
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 70d0d57204d..215bc03d0e9 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -11,14 +11,16 @@ module API
# Params:
# key_id - ssh key id for Git over SSH
# user_id - user id for Git over HTTP
+ # protocol - Git access protocol being used, e.g. HTTP or SSH
# project - project path with namespace
# action - git action (git-upload-pack or git-receive-pack)
- # ref - branch name
- # forced_push - forced_push
- # protocol - Git access protocol being used, e.g. HTTP or SSH
+ # changes - changes as "oldrev newrev ref", see Gitlab::ChangesList
post "/allowed" do
status 200
+ # Stores some Git-specific env thread-safely
+ Gitlab::Git::Env.set(parse_env)
+
actor =
if params[:key_id]
Key.find_by(id: params[:key_id])
@@ -30,18 +32,10 @@ module API
actor.update_last_used_at if actor.is_a?(Key)
- access =
- if wiki?
- Gitlab::GitAccessWiki.new(actor, project, protocol, authentication_abilities: ssh_authentication_abilities)
- else
- Gitlab::GitAccess.new(actor,
- project,
- protocol,
- authentication_abilities: ssh_authentication_abilities,
- env: parse_allowed_environment_variables)
- end
-
- access_status = access.check(params[:action], params[:changes])
+ access_checker = wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess
+ access_status = access_checker
+ .new(actor, project, protocol, authentication_abilities: ssh_authentication_abilities)
+ .check(params[:action], params[:changes])
response = { status: access_status.status, message: access_status.message }
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 09053e615cb..05423c17449 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -30,7 +30,7 @@ module API
use :pagination
end
- params :issue_params do
+ params :issue_params_ce do
optional :description, type: String, desc: 'The description of an issue'
optional :assignee_id, type: Integer, desc: 'The ID of a user to assign issue'
optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign issue'
@@ -38,6 +38,10 @@ module API
optional :due_date, type: String, desc: 'Date time string in the format YEAR-MONTH-DAY'
optional :confidential, type: Boolean, desc: 'Boolean parameter if the issue should be confidential'
end
+
+ params :issue_params do
+ use :issue_params_ce
+ end
end
resource :issues do
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index c8033664133..cb7aec47cf0 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -33,13 +33,17 @@ module API
end
end
- params :optional_params do
+ params :optional_params_ce do
optional :description, type: String, desc: 'The description of the merge request'
optional :assignee_id, type: Integer, desc: 'The ID of a user to assign the merge request'
optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign the merge request'
optional :labels, type: String, desc: 'Comma-separated list of label names'
optional :remove_source_branch, type: Boolean, desc: 'Remove source branch when merging'
end
+
+ params :optional_params do
+ use :optional_params_ce
+ end
end
desc 'List merge requests' do
@@ -145,14 +149,24 @@ module API
success Entities::MergeRequest
end
params do
+ # CE
+ at_least_one_of_ce = [
+ :assignee_id,
+ :description,
+ :labels,
+ :milestone_id,
+ :remove_source_branch,
+ :state_event,
+ :target_branch,
+ :title
+ ]
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],
desc: 'Status of the merge request'
+
use :optional_params
- at_least_one_of :title, :target_branch, :description, :assignee_id,
- :milestone_id, :labels, :state_event,
- :remove_source_branch
+ at_least_one_of(*at_least_one_of_ce)
end
put ':id/merge_requests/:merge_request_iid' do
merge_request = find_merge_request_with_access(params.delete(:merge_request_iid), :update_merge_request)
@@ -173,6 +187,7 @@ module API
success Entities::MergeRequest
end
params do
+ # CE
optional :merge_commit_message, type: String, desc: 'Custom merge commit message'
optional :should_remove_source_branch, type: Boolean,
desc: 'When true, the source branch will be deleted if possible'
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index de39e579ac3..e281e3230fd 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -78,7 +78,7 @@ module API
}
if can?(current_user, noteable_read_ability_name(noteable), noteable)
- if params[:created_at] && (current_user.is_admin? || user_project.owner == current_user)
+ if params[:created_at] && (current_user.admin? || user_project.owner == current_user)
opts[:created_at] = params[:created_at]
end
diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb
index 53791166c33..87dfd1573a4 100644
--- a/lib/api/project_hooks.rb
+++ b/lib/api/project_hooks.rb
@@ -13,7 +13,7 @@ module API
optional :merge_requests_events, type: Boolean, desc: "Trigger hook on merge request events"
optional :tag_push_events, type: Boolean, desc: "Trigger hook on tag push events"
optional :note_events, type: Boolean, desc: "Trigger hook on note(comment) events"
- optional :build_events, type: Boolean, desc: "Trigger hook on build events"
+ optional :job_events, type: Boolean, desc: "Trigger hook on job events"
optional :pipeline_events, type: Boolean, desc: "Trigger hook on pipeline events"
optional :wiki_page_events, type: Boolean, desc: "Trigger hook on wiki events"
optional :enable_ssl_verification, type: Boolean, desc: "Do SSL verification when triggering the hook"
@@ -53,7 +53,10 @@ module API
use :project_hook_properties
end
post ":id/hooks" do
- hook = user_project.hooks.new(declared_params(include_missing: false))
+ hook_params = declared_params(include_missing: false)
+ hook_params[:build_events] = hook_params.delete(:job_events) { false }
+
+ hook = user_project.hooks.new(hook_params)
if hook.save
present hook, with: Entities::ProjectHook
@@ -74,7 +77,10 @@ module API
put ":id/hooks/:hook_id" do
hook = user_project.hooks.find(params.delete(:hook_id))
- if hook.update_attributes(declared_params(include_missing: false))
+ update_params = declared_params(include_missing: false)
+ update_params[:build_events] = update_params.delete(:job_events) if update_params[:job_events]
+
+ if hook.update_attributes(update_params)
present hook, with: Entities::ProjectHook
else
error!("Invalid url given", 422) if hook.errors[:url].present?
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 766fbea53e6..50842370947 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -6,7 +6,7 @@ module API
before { authenticate_non_get! }
helpers do
- params :optional_params do
+ params :optional_params_ce do
optional :description, type: String, desc: 'The description of the project'
optional :issues_enabled, type: Boolean, desc: 'Flag indication if the issue tracker is enabled'
optional :merge_requests_enabled, type: Boolean, desc: 'Flag indication if merge requests are enabled'
@@ -22,6 +22,10 @@ module API
optional :only_allow_merge_if_pipeline_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed'
optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved'
end
+
+ params :optional_params do
+ use :optional_params_ce
+ end
end
resource :projects do
@@ -198,17 +202,33 @@ module API
success Entities::Project
end
params do
+ # CE
+ at_least_one_of_ce =
+ [
+ :builds_enabled,
+ :container_registry_enabled,
+ :default_branch,
+ :description,
+ :issues_enabled,
+ :lfs_enabled,
+ :merge_requests_enabled,
+ :name,
+ :only_allow_merge_if_all_discussions_are_resolved,
+ :only_allow_merge_if_pipeline_succeeds,
+ :path,
+ :public_builds,
+ :request_access_enabled,
+ :shared_runners_enabled,
+ :snippets_enabled,
+ :visibility,
+ :wiki_enabled,
+ ]
optional :name, type: String, desc: 'The name of the project'
optional :default_branch, type: String, desc: 'The default branch of the project'
optional :path, type: String, desc: 'The path of the repository'
+
use :optional_params
- 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, :visibility, :public_builds,
- :request_access_enabled, :only_allow_merge_if_pipeline_succeeds,
- :only_allow_merge_if_all_discussions_are_resolved, :path,
- :default_branch
+ at_least_one_of(*at_least_one_of_ce)
end
put ':id' do
authorize_admin_project
diff --git a/lib/api/runners.rb b/lib/api/runners.rb
index a77c876a749..db6c7c59092 100644
--- a/lib/api/runners.rb
+++ b/lib/api/runners.rb
@@ -161,18 +161,18 @@ module API
end
def authenticate_show_runner!(runner)
- return if runner.is_shared || current_user.is_admin?
+ return if runner.is_shared || current_user.admin?
forbidden!("No access granted") unless user_can_access_runner?(runner)
end
def authenticate_update_runner!(runner)
- return if current_user.is_admin?
+ return if current_user.admin?
forbidden!("Runner is shared") if runner.is_shared?
forbidden!("No access granted") unless user_can_access_runner?(runner)
end
def authenticate_delete_runner!(runner)
- return if current_user.is_admin?
+ return if current_user.admin?
forbidden!("Runner is shared") if runner.is_shared?
forbidden!("Runner associated with more than one project") if runner.projects.count > 1
forbidden!("No access granted") unless user_can_access_runner?(runner)
@@ -181,7 +181,7 @@ module API
def authenticate_enable_runner!(runner)
forbidden!("Runner is shared") if runner.is_shared?
forbidden!("Runner is locked") if runner.locked?
- return if current_user.is_admin?
+ return if current_user.admin?
forbidden!("No access granted") unless user_can_access_runner?(runner)
end
diff --git a/lib/api/services.rb b/lib/api/services.rb
index 65f86caaa51..23ef62c2258 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -642,7 +642,7 @@ module API
service_params = declared_params(include_missing: false).merge(active: true)
if service.update_attributes(service_params)
- present service, with: Entities::ProjectService, include_passwords: current_user.is_admin?
+ present service, with: Entities::ProjectService, include_passwords: current_user.admin?
else
render_api_error!('400 Bad Request', 400)
end
@@ -673,7 +673,7 @@ module API
end
get ":id/services/:service_slug" do
service = user_project.find_or_initialize_service(params[:service_slug].underscore)
- present service, with: Entities::ProjectService, include_passwords: current_user.is_admin?
+ present service, with: Entities::ProjectService, include_passwords: current_user.admin?
end
end
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index c7f97ad2aab..d01c7f2703b 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -20,6 +20,55 @@ module API
success Entities::ApplicationSetting
end
params do
+ # CE
+ at_least_one_of_ce = [
+ :admin_notification_email,
+ :after_sign_out_path,
+ :after_sign_up_text,
+ :akismet_enabled,
+ :container_registry_token_expire_delay,
+ :default_artifacts_expire_in,
+ :default_branch_protection,
+ :default_group_visibility,
+ :default_project_visibility,
+ :default_projects_limit,
+ :default_snippet_visibility,
+ :disabled_oauth_sign_in_sources,
+ :domain_blacklist_enabled,
+ :domain_whitelist,
+ :email_author_in_body,
+ :enabled_git_access_protocol,
+ :gravatar_enabled,
+ :help_page_text,
+ :home_page_url,
+ :housekeeping_enabled,
+ :html_emails_enabled,
+ :import_sources,
+ :koding_enabled,
+ :max_artifacts_size,
+ :max_attachment_size,
+ :max_pages_size,
+ :metrics_enabled,
+ :plantuml_enabled,
+ :polling_interval_multiplier,
+ :recaptcha_enabled,
+ :repository_checks_enabled,
+ :repository_storage,
+ :require_two_factor_authentication,
+ :restricted_visibility_levels,
+ :send_user_confirmation_email,
+ :sentry_enabled,
+ :session_expire_delay,
+ :shared_runners_enabled,
+ :sidekiq_throttling_enabled,
+ :sign_in_text,
+ :signin_enabled,
+ :signup_enabled,
+ :terminal_max_session_time,
+ :user_default_external,
+ :user_oauth_applications,
+ :version_check_enabled
+ ]
optional :default_branch_protection, type: Integer, values: [0, 1, 2], desc: 'Determine if developers can push to master'
optional :default_project_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default project visibility'
optional :default_snippet_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default snippet visibility'
@@ -111,22 +160,8 @@ module API
end
optional :terminal_max_session_time, type: Integer, desc: 'Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time.'
optional :polling_interval_multiplier, type: BigDecimal, desc: 'Interval multiplier used by endpoints that perform polling. Set to 0 to disable polling.'
- at_least_one_of :default_branch_protection, :default_project_visibility, :default_snippet_visibility,
- :default_group_visibility, :restricted_visibility_levels, :import_sources,
- :enabled_git_access_protocol, :gravatar_enabled, :default_projects_limit,
- :max_attachment_size, :session_expire_delay, :disabled_oauth_sign_in_sources,
- :user_oauth_applications, :user_default_external, :signup_enabled,
- :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,
- :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,
- :version_check_enabled, :email_author_in_body, :html_emails_enabled,
- :housekeeping_enabled, :terminal_max_session_time, :polling_interval_multiplier
+
+ at_least_one_of(*at_least_one_of_ce)
end
put "application/settings" do
attrs = declared_params(include_missing: false)
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 992a751b37d..eedc59f8636 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -37,11 +37,13 @@ module API
success Entities::UserBasic
end
params do
+ # CE
optional :username, type: String, desc: 'Get a single user with a specific username'
optional :search, type: String, desc: 'Search for a username'
optional :active, type: Boolean, default: false, desc: 'Filters only active users'
optional :external, type: Boolean, default: false, desc: 'Filters only external users'
optional :blocked, type: Boolean, default: false, desc: 'Filters only blocked users'
+
use :pagination
end
get do
@@ -56,10 +58,10 @@ module API
users = users.active if params[:active]
users = users.search(params[:search]) if params[:search].present?
users = users.blocked if params[:blocked]
- users = users.external if params[:external] && current_user.is_admin?
+ users = users.external if params[:external] && current_user.admin?
end
- entity = current_user.is_admin? ? Entities::UserPublic : Entities::UserBasic
+ entity = current_user.admin? ? Entities::UserPublic : Entities::UserBasic
present paginate(users), with: entity
end
@@ -73,7 +75,7 @@ module API
user = User.find_by(id: params[:id])
not_found!('User') unless user
- if current_user && current_user.is_admin?
+ if current_user && current_user.admin?
present user, with: Entities::UserPublic
elsif can?(current_user, :read_user, user)
present user, with: Entities::User
diff --git a/lib/api/v3/groups.rb b/lib/api/v3/groups.rb
index 9b27411ae21..63d464b926b 100644
--- a/lib/api/v3/groups.rb
+++ b/lib/api/v3/groups.rb
@@ -54,7 +54,7 @@ module API
groups = groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present?
groups = groups.reorder(params[:order_by] => params[:sort])
- present_groups groups, statistics: params[:statistics] && current_user.is_admin?
+ present_groups groups, statistics: params[:statistics] && current_user.admin?
end
desc 'Get list of owned groups for authenticated user' do
diff --git a/lib/api/v3/notes.rb b/lib/api/v3/notes.rb
index 4f8e0eff4ff..009ec5c6bbd 100644
--- a/lib/api/v3/notes.rb
+++ b/lib/api/v3/notes.rb
@@ -79,7 +79,7 @@ module API
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)
+ if params[:created_at] && (current_user.admin? || user_project.owner == current_user)
opts[:created_at] = params[:created_at]
end
diff --git a/lib/api/v3/runners.rb b/lib/api/v3/runners.rb
index 1934d6e578c..faa265f3314 100644
--- a/lib/api/v3/runners.rb
+++ b/lib/api/v3/runners.rb
@@ -50,7 +50,7 @@ module API
helpers do
def authenticate_delete_runner!(runner)
- return if current_user.is_admin?
+ return if current_user.admin?
forbidden!("Runner is shared") if runner.is_shared?
forbidden!("Runner associated with more than one project") if runner.projects.count > 1
forbidden!("No access granted") unless user_can_access_runner?(runner)
diff --git a/lib/api/v3/services.rb b/lib/api/v3/services.rb
index bbe07ed4212..61629a04174 100644
--- a/lib/api/v3/services.rb
+++ b/lib/api/v3/services.rb
@@ -602,7 +602,7 @@ module API
end
get ":id/services/:service_slug" do
service = user_project.find_or_initialize_service(params[:service_slug].underscore)
- present service, with: Entities::ProjectService, include_passwords: current_user.is_admin?
+ present service, with: Entities::ProjectService, include_passwords: current_user.admin?
end
end
diff --git a/lib/banzai/filter/issuable_state_filter.rb b/lib/banzai/filter/issuable_state_filter.rb
index 6b78aa795b4..0b2b8bd7f4d 100644
--- a/lib/banzai/filter/issuable_state_filter.rb
+++ b/lib/banzai/filter/issuable_state_filter.rb
@@ -13,8 +13,8 @@ module Banzai
issuables = extractor.extract([doc])
issuables.each do |node, issuable|
- if VISIBLE_STATES.include?(issuable.state)
- node.children.last.content += " [#{issuable.state}]"
+ if VISIBLE_STATES.include?(issuable.state) && node.children.present?
+ node.add_child(Nokogiri::XML::Text.new(" [#{issuable.state}]", doc))
end
end
diff --git a/lib/banzai/reference_parser/base_parser.rb b/lib/banzai/reference_parser/base_parser.rb
index dabf71d6aeb..c2503fa2adc 100644
--- a/lib/banzai/reference_parser/base_parser.rb
+++ b/lib/banzai/reference_parser/base_parser.rb
@@ -136,7 +136,8 @@ module Banzai
nodes.each_with_object({}) do |node, hash|
if node.has_attribute?(attribute)
- hash[node] = objects_by_id[node.attr(attribute).to_i]
+ obj = objects_by_id[node.attr(attribute).to_i]
+ hash[node] = obj if obj
end
end
end
diff --git a/lib/banzai/reference_parser/merge_request_parser.rb b/lib/banzai/reference_parser/merge_request_parser.rb
index 7d7dcce017e..84a28b33d7c 100644
--- a/lib/banzai/reference_parser/merge_request_parser.rb
+++ b/lib/banzai/reference_parser/merge_request_parser.rb
@@ -3,23 +3,42 @@ module Banzai
class MergeRequestParser < BaseParser
self.reference_type = :merge_request
+ def nodes_visible_to_user(user, nodes)
+ merge_requests = merge_requests_for_nodes(nodes)
+
+ nodes.select do |node|
+ merge_request = merge_requests[node]
+
+ merge_request && can?(user, :read_merge_request, merge_request.project)
+ end
+ end
+
+ def referenced_by(nodes)
+ merge_requests = merge_requests_for_nodes(nodes)
+
+ nodes.map { |node| merge_requests[node] }.compact.uniq
+ end
+
def merge_requests_for_nodes(nodes)
@merge_requests_for_nodes ||= grouped_objects_for_nodes(
nodes,
- MergeRequest.all,
+ MergeRequest.includes(
+ :author,
+ :assignee,
+ {
+ # These associations are primarily used for checking permissions.
+ # Eager loading these ensures we don't end up running dozens of
+ # queries in this process.
+ target_project: [
+ { namespace: :owner },
+ { group: [:owners, :group_members] },
+ :invited_groups,
+ :project_members
+ ]
+ }),
self.class.data_attribute
)
end
-
- def references_relation
- MergeRequest.includes(:author, :assignee, :target_project)
- end
-
- private
-
- def can_read_reference?(user, ref_project)
- can?(user, :read_merge_request, ref_project)
- end
end
end
end
diff --git a/lib/bitbucket/representation/base.rb b/lib/bitbucket/representation/base.rb
index 94adaacc9b5..800d5a075c6 100644
--- a/lib/bitbucket/representation/base.rb
+++ b/lib/bitbucket/representation/base.rb
@@ -1,6 +1,8 @@
module Bitbucket
module Representation
class Base
+ attr_reader :raw
+
def initialize(raw)
@raw = raw
end
@@ -8,10 +10,6 @@ module Bitbucket
def self.decorate(entries)
entries.map { |entry| new(entry)}
end
-
- private
-
- attr_reader :raw
end
end
end
diff --git a/lib/ci/ansi2html.rb b/lib/ci/ansi2html.rb
index 1020452480a..b439b0ee29b 100644
--- a/lib/ci/ansi2html.rb
+++ b/lib/ci/ansi2html.rb
@@ -172,7 +172,7 @@ module Ci
close_open_tags()
OpenStruct.new(
- html: @out,
+ html: @out.force_encoding(Encoding.default_external),
state: state,
append: append,
truncated: truncated,
diff --git a/lib/container_registry/path.rb b/lib/container_registry/path.rb
index a4b5f2aba6c..4a585996aa5 100644
--- a/lib/container_registry/path.rb
+++ b/lib/container_registry/path.rb
@@ -15,7 +15,7 @@ module ContainerRegistry
LEVELS_SUPPORTED = 3
def initialize(path)
- @path = path
+ @path = path.to_s.downcase
end
def valid?
@@ -25,7 +25,7 @@ module ContainerRegistry
end
def components
- @components ||= @path.to_s.split('/')
+ @components ||= @path.split('/')
end
def nodes
diff --git a/lib/container_registry/tag.rb b/lib/container_registry/tag.rb
index d00e6191e7e..728deea224f 100644
--- a/lib/container_registry/tag.rb
+++ b/lib/container_registry/tag.rb
@@ -29,6 +29,10 @@ module ContainerRegistry
"#{repository.path}:#{name}"
end
+ def location
+ "#{repository.location}:#{name}"
+ end
+
def [](key)
return unless manifest
diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb
index f4efa20374a..5a6d9ae99a0 100644
--- a/lib/gitlab/bitbucket_import/importer.rb
+++ b/lib/gitlab/bitbucket_import/importer.rb
@@ -149,7 +149,7 @@ module Gitlab
description += @formatter.author_line(pull_request.author) unless find_user_id(pull_request.author)
description += pull_request.description
- merge_request = project.merge_requests.create(
+ merge_request = project.merge_requests.create!(
iid: pull_request.iid,
title: pull_request.title,
description: description,
@@ -168,7 +168,7 @@ module Gitlab
import_pull_request_comments(pull_request, merge_request) if merge_request.persisted?
rescue StandardError => e
- errors << { type: :pull_request, iid: pull_request.iid, errors: e.message }
+ errors << { type: :pull_request, iid: pull_request.iid, errors: e.message, trace: e.backtrace.join("\n"), raw_response: pull_request.raw }
end
end
end
diff --git a/lib/gitlab/checks/change_access.rb b/lib/gitlab/checks/change_access.rb
index eb2f2e144fd..8793b20aa35 100644
--- a/lib/gitlab/checks/change_access.rb
+++ b/lib/gitlab/checks/change_access.rb
@@ -5,7 +5,7 @@ module Gitlab
attr_reader :user_access, :project, :skip_authorization, :protocol
def initialize(
- change, user_access:, project:, env: {}, skip_authorization: false,
+ change, user_access:, project:, skip_authorization: false,
protocol:
)
@oldrev, @newrev, @ref = change.values_at(:oldrev, :newrev, :ref)
@@ -13,7 +13,6 @@ module Gitlab
@tag_name = Gitlab::Git.tag_name(@ref)
@user_access = user_access
@project = project
- @env = env
@skip_authorization = skip_authorization
@protocol = protocol
end
@@ -97,7 +96,7 @@ module Gitlab
end
def forced_push?
- Gitlab::Checks::ForcePush.force_push?(@project, @oldrev, @newrev, env: @env)
+ Gitlab::Checks::ForcePush.force_push?(@project, @oldrev, @newrev)
end
def update?
diff --git a/lib/gitlab/checks/force_push.rb b/lib/gitlab/checks/force_push.rb
index de0c9049ebf..1e73f89158d 100644
--- a/lib/gitlab/checks/force_push.rb
+++ b/lib/gitlab/checks/force_push.rb
@@ -1,20 +1,16 @@
module Gitlab
module Checks
class ForcePush
- def self.force_push?(project, oldrev, newrev, env: {})
+ def self.force_push?(project, oldrev, newrev)
return false if project.empty_repo?
# Created or deleted branch
if Gitlab::Git.blank_ref?(oldrev) || Gitlab::Git.blank_ref?(newrev)
false
else
- missed_ref, exit_status = Gitlab::Git::RevList.new(oldrev, newrev, project: project, env: env).execute
-
- if exit_status == 0
- missed_ref.present?
- else
- raise "Got a non-zero exit code while calling out to `git rev-list` in the force-push check."
- end
+ Gitlab::Git::RevList.new(
+ path_to_repo: project.repository.path_to_repo,
+ oldrev: oldrev, newrev: newrev).missed_ref.present?
end
end
end
diff --git a/lib/gitlab/ci/trace/stream.rb b/lib/gitlab/ci/trace/stream.rb
index 2af94e2c60e..fa462cbe095 100644
--- a/lib/gitlab/ci/trace/stream.rb
+++ b/lib/gitlab/ci/trace/stream.rb
@@ -4,7 +4,7 @@ module Gitlab
# This was inspired from: http://stackoverflow.com/a/10219411/1520132
class Stream
BUFFER_SIZE = 4096
- LIMIT_SIZE = 50.kilobytes
+ LIMIT_SIZE = 500.kilobytes
attr_reader :stream
@@ -14,6 +14,7 @@ module Gitlab
def initialize
@stream = yield
+ @stream&.binmode
end
def valid?
@@ -25,11 +26,10 @@ module Gitlab
end
def limit(last_bytes = LIMIT_SIZE)
- stream_size = size
- if stream_size < last_bytes
- last_bytes = stream_size
+ if last_bytes < size
+ stream.seek(-last_bytes, IO::SEEK_END)
+ stream.readline
end
- stream.seek(-last_bytes, IO::SEEK_END)
end
def append(data, offset)
@@ -52,7 +52,7 @@ module Gitlab
read_last_lines(last_lines)
else
stream.read
- end
+ end.force_encoding(Encoding.default_external)
end
def html_with_state(state = nil)
@@ -61,8 +61,8 @@ module Gitlab
def html(last_lines: nil)
text = raw(last_lines: last_lines)
- stream = StringIO.new(text)
- ::Ci::Ansi2html.convert(stream).html
+ buffer = StringIO.new(text)
+ ::Ci::Ansi2html.convert(buffer).html
end
def extract_coverage(regex)
@@ -76,11 +76,14 @@ module Gitlab
stream.each_line do |line|
matches = line.scan(regex)
next unless matches.is_a?(Array)
+ next if matches.empty?
match = matches.flatten.last
coverage = match.gsub(/\d+(\.\d+)?/).first
- return coverage.to_f if coverage.present?
+ return coverage if coverage.present?
end
+
+ nil
rescue
# if bad regex or something goes wrong we dont want to interrupt transition
# so we just silentrly ignore error for now
@@ -111,7 +114,6 @@ module Gitlab
end
chunks.join.lines.last(last_lines).join
- .force_encoding(Encoding.default_external)
end
end
end
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index 63b8d0d3b9d..d0bd1299671 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -57,16 +57,16 @@ module Gitlab
postgresql? ? "RANDOM()" : "RAND()"
end
- def true_value
- if Gitlab::Database.postgresql?
+ def self.true_value
+ if postgresql?
"'t'"
else
1
end
end
- def false_value
- if Gitlab::Database.postgresql?
+ def self.false_value
+ if postgresql?
"'f'"
else
0
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 525aa920328..6dabbe0264c 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -89,7 +89,8 @@ module Gitlab
ADD CONSTRAINT #{key_name}
FOREIGN KEY (#{column})
REFERENCES #{target} (id)
- ON DELETE #{on_delete} NOT VALID;
+ #{on_delete ? "ON DELETE #{on_delete}" : ''}
+ NOT VALID;
EOF
# Validate the existing constraint. This can potentially take a very
@@ -114,6 +115,14 @@ module Gitlab
execute('SET statement_timeout TO 0') if Database.postgresql?
end
+ def true_value
+ Database.true_value
+ end
+
+ def false_value
+ Database.false_value
+ end
+
# Updates the value of a column in batches.
#
# This method updates the table in batches of 5% of the total row count.
@@ -250,6 +259,245 @@ module Gitlab
raise error
end
end
+
+ # Renames a column without requiring downtime.
+ #
+ # Concurrent renames work by using database triggers to ensure both the
+ # old and new column are in sync. However, this method will _not_ remove
+ # the triggers or the old column automatically; this needs to be done
+ # manually in a post-deployment migration. This can be done using the
+ # method `cleanup_concurrent_column_rename`.
+ #
+ # table - The name of the database table containing the column.
+ # old - The old column name.
+ # new - The new column name.
+ # type - The type of the new column. If no type is given the old column's
+ # type is used.
+ def rename_column_concurrently(table, old, new, type: nil)
+ if transaction_open?
+ raise 'rename_column_concurrently can not be run inside a transaction'
+ end
+
+ trigger_name = rename_trigger_name(table, old, new)
+ quoted_table = quote_table_name(table)
+ quoted_old = quote_column_name(old)
+ quoted_new = quote_column_name(new)
+
+ if Database.postgresql?
+ install_rename_triggers_for_postgresql(trigger_name, quoted_table,
+ quoted_old, quoted_new)
+ else
+ install_rename_triggers_for_mysql(trigger_name, quoted_table,
+ quoted_old, quoted_new)
+ end
+
+ old_col = column_for(table, old)
+ new_type = type || old_col.type
+
+ add_column(table, new, new_type,
+ limit: old_col.limit,
+ default: old_col.default,
+ null: old_col.null,
+ precision: old_col.precision,
+ scale: old_col.scale)
+
+ update_column_in_batches(table, new, Arel::Table.new(table)[old])
+
+ copy_indexes(table, old, new)
+ copy_foreign_keys(table, old, new)
+ end
+
+ # Changes the type of a column concurrently.
+ #
+ # table - The table containing the column.
+ # column - The name of the column to change.
+ # new_type - The new column type.
+ def change_column_type_concurrently(table, column, new_type)
+ temp_column = "#{column}_for_type_change"
+
+ rename_column_concurrently(table, column, temp_column, type: new_type)
+ end
+
+ # Performs cleanup of a concurrent type change.
+ #
+ # table - The table containing the column.
+ # column - The name of the column to change.
+ # new_type - The new column type.
+ def cleanup_concurrent_column_type_change(table, column)
+ temp_column = "#{column}_for_type_change"
+
+ transaction do
+ # This has to be performed in a transaction as otherwise we might have
+ # inconsistent data.
+ cleanup_concurrent_column_rename(table, column, temp_column)
+ rename_column(table, temp_column, column)
+ end
+ end
+
+ # Cleans up a concurrent column name.
+ #
+ # This method takes care of removing previously installed triggers as well
+ # as removing the old column.
+ #
+ # table - The name of the database table.
+ # old - The name of the old column.
+ # new - The name of the new column.
+ def cleanup_concurrent_column_rename(table, old, new)
+ trigger_name = rename_trigger_name(table, old, new)
+
+ if Database.postgresql?
+ remove_rename_triggers_for_postgresql(table, trigger_name)
+ else
+ remove_rename_triggers_for_mysql(trigger_name)
+ end
+
+ remove_column(table, old)
+ end
+
+ # Performs a concurrent column rename when using PostgreSQL.
+ def install_rename_triggers_for_postgresql(trigger, table, old, new)
+ execute <<-EOF.strip_heredoc
+ CREATE OR REPLACE FUNCTION #{trigger}()
+ RETURNS trigger AS
+ $BODY$
+ BEGIN
+ NEW.#{new} := NEW.#{old};
+ RETURN NEW;
+ END;
+ $BODY$
+ LANGUAGE 'plpgsql'
+ VOLATILE
+ EOF
+
+ execute <<-EOF.strip_heredoc
+ CREATE TRIGGER #{trigger}
+ BEFORE INSERT OR UPDATE
+ ON #{table}
+ FOR EACH ROW
+ EXECUTE PROCEDURE #{trigger}()
+ EOF
+ end
+
+ # Installs the triggers necessary to perform a concurrent column rename on
+ # MySQL.
+ def install_rename_triggers_for_mysql(trigger, table, old, new)
+ execute <<-EOF.strip_heredoc
+ CREATE TRIGGER #{trigger}_insert
+ BEFORE INSERT
+ ON #{table}
+ FOR EACH ROW
+ SET NEW.#{new} = NEW.#{old}
+ EOF
+
+ execute <<-EOF.strip_heredoc
+ CREATE TRIGGER #{trigger}_update
+ BEFORE UPDATE
+ ON #{table}
+ FOR EACH ROW
+ SET NEW.#{new} = NEW.#{old}
+ EOF
+ end
+
+ # Removes the triggers used for renaming a PostgreSQL column concurrently.
+ def remove_rename_triggers_for_postgresql(table, trigger)
+ execute("DROP TRIGGER #{trigger} ON #{table}")
+ execute("DROP FUNCTION #{trigger}()")
+ end
+
+ # Removes the triggers used for renaming a MySQL column concurrently.
+ def remove_rename_triggers_for_mysql(trigger)
+ execute("DROP TRIGGER #{trigger}_insert")
+ execute("DROP TRIGGER #{trigger}_update")
+ end
+
+ # Returns the (base) name to use for triggers when renaming columns.
+ def rename_trigger_name(table, old, new)
+ 'trigger_' + Digest::SHA256.hexdigest("#{table}_#{old}_#{new}").first(12)
+ end
+
+ # Returns an Array containing the indexes for the given column
+ def indexes_for(table, column)
+ column = column.to_s
+
+ indexes(table).select { |index| index.columns.include?(column) }
+ end
+
+ # Returns an Array containing the foreign keys for the given column.
+ def foreign_keys_for(table, column)
+ column = column.to_s
+
+ foreign_keys(table).select { |fk| fk.column == column }
+ end
+
+ # Copies all indexes for the old column to a new column.
+ #
+ # table - The table containing the columns and indexes.
+ # old - The old column.
+ # new - The new column.
+ def copy_indexes(table, old, new)
+ old = old.to_s
+ new = new.to_s
+
+ indexes_for(table, old).each do |index|
+ new_columns = index.columns.map do |column|
+ column == old ? new : column
+ end
+
+ # This is necessary as we can't properly rename indexes such as
+ # "ci_taggings_idx".
+ unless index.name.include?(old)
+ raise "The index #{index.name} can not be copied as it does not "\
+ "mention the old column. You have to rename this index manually first."
+ end
+
+ name = index.name.gsub(old, new)
+
+ options = {
+ unique: index.unique,
+ name: name,
+ length: index.lengths,
+ order: index.orders
+ }
+
+ # These options are not supported by MySQL, so we only add them if
+ # they were previously set.
+ options[:using] = index.using if index.using
+ options[:where] = index.where if index.where
+
+ unless index.opclasses.blank?
+ opclasses = index.opclasses.dup
+
+ # Copy the operator classes for the old column (if any) to the new
+ # column.
+ opclasses[new] = opclasses.delete(old) if opclasses[old]
+
+ options[:opclasses] = opclasses
+ end
+
+ add_concurrent_index(table, new_columns, options)
+ end
+ end
+
+ # Copies all foreign keys for the old column to the new column.
+ #
+ # table - The table containing the columns and indexes.
+ # old - The old column.
+ # new - The new column.
+ def copy_foreign_keys(table, old, new)
+ foreign_keys_for(table, old).each do |fk|
+ add_concurrent_foreign_key(fk.from_table,
+ fk.to_table,
+ column: new,
+ on_delete: fk.on_delete)
+ end
+ end
+
+ # Returns the column for the given table and column name.
+ def column_for(table, name)
+ name = name.to_s
+
+ columns(table).find { |column| column.name == name }
+ end
end
end
end
diff --git a/lib/gitlab/database/multi_threaded_migration.rb b/lib/gitlab/database/multi_threaded_migration.rb
new file mode 100644
index 00000000000..7ae5a4c17c8
--- /dev/null
+++ b/lib/gitlab/database/multi_threaded_migration.rb
@@ -0,0 +1,52 @@
+module Gitlab
+ module Database
+ module MultiThreadedMigration
+ MULTI_THREAD_AR_CONNECTION = :thread_local_ar_connection
+
+ # This overwrites the default connection method so that every thread can
+ # use a thread-local connection, while still supporting all of Rails'
+ # migration methods.
+ def connection
+ Thread.current[MULTI_THREAD_AR_CONNECTION] ||
+ ActiveRecord::Base.connection
+ end
+
+ # Starts a thread-pool for N threads, along with N threads each using a
+ # single connection. The provided block is yielded from inside each
+ # thread.
+ #
+ # Example:
+ #
+ # with_multiple_threads(4) do
+ # execute('SELECT ...')
+ # end
+ #
+ # thread_count - The number of threads to start.
+ #
+ # join - When set to true this method will join the threads, blocking the
+ # caller until all threads have finished running.
+ #
+ # Returns an Array containing the started threads.
+ def with_multiple_threads(thread_count, join: true)
+ pool = Gitlab::Database.create_connection_pool(thread_count)
+
+ threads = Array.new(thread_count) do
+ Thread.new do
+ pool.with_connection do |connection|
+ begin
+ Thread.current[MULTI_THREAD_AR_CONNECTION] = connection
+ yield
+ ensure
+ Thread.current[MULTI_THREAD_AR_CONNECTION] = nil
+ end
+ end
+ end
+ end
+
+ threads.each(&:join) if join
+
+ threads
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/diff_refs.rb b/lib/gitlab/diff/diff_refs.rb
index 8406ca4269c..7948782aecc 100644
--- a/lib/gitlab/diff/diff_refs.rb
+++ b/lib/gitlab/diff/diff_refs.rb
@@ -18,6 +18,12 @@ module Gitlab
head_sha == other.head_sha
end
+ alias_method :eql?, :==
+
+ def hash
+ [base_sha, start_sha, head_sha].hash
+ end
+
# There is only one case in which we will have `start_sha` and `head_sha`,
# but not `base_sha`, which is when a diff is generated between an
# orphaned branch and another branch, which means there _is_ no base, but
diff --git a/lib/gitlab/etag_caching/middleware.rb b/lib/gitlab/etag_caching/middleware.rb
index 11167632e07..270d67dd50c 100644
--- a/lib/gitlab/etag_caching/middleware.rb
+++ b/lib/gitlab/etag_caching/middleware.rb
@@ -1,40 +1,12 @@
module Gitlab
module EtagCaching
class Middleware
- RESERVED_WORDS = NamespaceValidator::WILDCARD_ROUTES.map { |word| "/#{word}/" }.join('|')
- ROUTES = [
- {
- regexp: %r(^(?!.*(#{RESERVED_WORDS})).*/noteable/issue/\d+/notes\z),
- name: 'issue_notes'
- },
- {
- regexp: %r(^(?!.*(#{RESERVED_WORDS})).*/issues/\d+/rendered_title\z),
- name: 'issue_title'
- },
- {
- regexp: %r(^(?!.*(#{RESERVED_WORDS})).*/pipelines\.json\z),
- name: 'project_pipelines'
- },
- {
- regexp: %r(^(?!.*(#{RESERVED_WORDS})).*/commit/\s+/pipelines\.json\z),
- name: 'commit_pipelines'
- },
- {
- regexp: %r(^(?!.*(#{RESERVED_WORDS})).*/merge_requests/new\.json\z),
- name: 'new_merge_request_pipelines'
- },
- {
- regexp: %r(^(?!.*(#{RESERVED_WORDS})).*/merge_requests/\d+/pipelines\.json\z),
- name: 'merge_request_pipelines'
- }
- ].freeze
-
def initialize(app)
@app = app
end
def call(env)
- route = match_current_route(env)
+ route = Gitlab::EtagCaching::Router.match(env)
return @app.call(env) unless route
track_event(:etag_caching_middleware_used, route)
@@ -55,10 +27,6 @@ module Gitlab
private
- def match_current_route(env)
- ROUTES.find { |route| route[:regexp].match(env['PATH_INFO']) }
- end
-
def get_etag(env)
cache_key = env['PATH_INFO']
store = Gitlab::EtagCaching::Store.new
@@ -95,7 +63,7 @@ module Gitlab
end
def track_event(name, route)
- Gitlab::Metrics.add_event(name, endpoint: route[:name])
+ Gitlab::Metrics.add_event(name, endpoint: route.name)
end
end
end
diff --git a/lib/gitlab/etag_caching/router.rb b/lib/gitlab/etag_caching/router.rb
new file mode 100644
index 00000000000..f6e4f279c06
--- /dev/null
+++ b/lib/gitlab/etag_caching/router.rb
@@ -0,0 +1,39 @@
+module Gitlab
+ module EtagCaching
+ class Router
+ Route = Struct.new(:regexp, :name)
+
+ RESERVED_WORDS = NamespaceValidator::WILDCARD_ROUTES.map { |word| "/#{word}/" }.join('|')
+ ROUTES = [
+ Gitlab::EtagCaching::Router::Route.new(
+ %r(^(?!.*(#{RESERVED_WORDS})).*/noteable/issue/\d+/notes\z),
+ 'issue_notes'
+ ),
+ Gitlab::EtagCaching::Router::Route.new(
+ %r(^(?!.*(#{RESERVED_WORDS})).*/issues/\d+/rendered_title\z),
+ 'issue_title'
+ ),
+ Gitlab::EtagCaching::Router::Route.new(
+ %r(^(?!.*(#{RESERVED_WORDS})).*/commit/\S+/pipelines\.json\z),
+ 'commit_pipelines'
+ ),
+ Gitlab::EtagCaching::Router::Route.new(
+ %r(^(?!.*(#{RESERVED_WORDS})).*/merge_requests/new\.json\z),
+ 'new_merge_request_pipelines'
+ ),
+ Gitlab::EtagCaching::Router::Route.new(
+ %r(^(?!.*(#{RESERVED_WORDS})).*/merge_requests/\d+/pipelines\.json\z),
+ 'merge_request_pipelines'
+ ),
+ Gitlab::EtagCaching::Router::Route.new(
+ %r(^(?!.*(#{RESERVED_WORDS})).*/pipelines\.json\z),
+ 'project_pipelines'
+ )
+ ].freeze
+
+ def self.match(env)
+ ROUTES.find { |route| route.regexp.match(env['PATH_INFO']) }
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb
index e56eb0d3beb..98fd4e78126 100644
--- a/lib/gitlab/git/blob.rb
+++ b/lib/gitlab/git/blob.rb
@@ -8,7 +8,7 @@ module Gitlab
# the user. We load as much as we can for encoding detection
# (Linguist) and LFS pointer parsing. All other cases where we need full
# blob data should use load_all_data!.
- MAX_DATA_DISPLAY_SIZE = 10485760
+ MAX_DATA_DISPLAY_SIZE = 10.megabytes
attr_accessor :name, :path, :size, :data, :mode, :id, :commit_id, :loaded_size, :binary
@@ -153,7 +153,7 @@ module Gitlab
def lfs_size
if has_lfs_version_key?
size = data.match(/(?<=size )([0-9]+)/)
- return size[1] if size
+ return size[1].to_i if size
end
nil
diff --git a/lib/gitlab/git/env.rb b/lib/gitlab/git/env.rb
new file mode 100644
index 00000000000..0fdc57ec954
--- /dev/null
+++ b/lib/gitlab/git/env.rb
@@ -0,0 +1,38 @@
+module Gitlab
+ module Git
+ # Ephemeral (per request) storage for environment variables that some Git
+ # commands may need.
+ #
+ # For example, in pre-receive hooks, new objects are put in a temporary
+ # $GIT_OBJECT_DIRECTORY. Without it set, the new objects cannot be retrieved
+ # (this would break push rules for instance).
+ #
+ # This class is thread-safe via RequestStore.
+ class Env
+ WHITELISTED_GIT_VARIABLES = %w[
+ GIT_OBJECT_DIRECTORY
+ GIT_ALTERNATE_OBJECT_DIRECTORIES
+ ].freeze
+
+ def self.set(env)
+ return unless RequestStore.active?
+
+ RequestStore.store[:gitlab_git_env] = whitelist_git_env(env)
+ end
+
+ def self.all
+ return {} unless RequestStore.active?
+
+ RequestStore.fetch(:gitlab_git_env) { {} }
+ end
+
+ def self.[](key)
+ all[key]
+ end
+
+ def self.whitelist_git_env(env)
+ env.select { |key, _| WHITELISTED_GIT_VARIABLES.include?(key.to_s) }.with_indifferent_access
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index fc473b2c21e..d7dac9f6149 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -8,6 +8,10 @@ module Gitlab
class Repository
include Gitlab::Git::Popen
+ ALLOWED_OBJECT_DIRECTORIES_VARIABLES = %w[
+ GIT_OBJECT_DIRECTORY
+ GIT_ALTERNATE_OBJECT_DIRECTORIES
+ ].freeze
SEARCH_CONTEXT_LINES = 3
NoRepository = Class.new(StandardError)
@@ -41,13 +45,15 @@ module Gitlab
# Default branch in the repository
def root_ref
- @root_ref ||= Gitlab::GitalyClient.migrate(:root_ref) do |is_enabled|
- if is_enabled
- gitaly_ref_client.default_branch_name
- else
- discover_default_branch
- end
- end
+ # NOTE: This feature is intentionally disabled until
+ # https://gitlab.com/gitlab-org/gitaly/issues/179 is resolved
+ # @root_ref ||= Gitlab::GitalyClient.migrate(:root_ref) do |is_enabled|
+ # if is_enabled
+ # gitaly_ref_client.default_branch_name
+ # else
+ @root_ref ||= discover_default_branch
+ # end
+ # end
rescue GRPC::BadStatus => e
raise CommandError.new(e)
end
@@ -58,7 +64,7 @@ module Gitlab
end
def rugged
- @rugged ||= Rugged::Repository.new(path)
+ @rugged ||= Rugged::Repository.new(path, alternates: alternate_object_directories)
rescue Rugged::RepositoryError, Rugged::OSError
raise NoRepository.new('no repository for such path')
end
@@ -66,13 +72,15 @@ module Gitlab
# Returns an Array of branch names
# sorted by name ASC
def branch_names
- Gitlab::GitalyClient.migrate(:branch_names) do |is_enabled|
- if is_enabled
- gitaly_ref_client.branch_names
- else
- branches.map(&:name)
- end
- end
+ # Gitlab::GitalyClient.migrate(:branch_names) do |is_enabled|
+ # NOTE: This feature is intentionally disabled until
+ # https://gitlab.com/gitlab-org/gitaly/issues/179 is resolved
+ # if is_enabled
+ # gitaly_ref_client.branch_names
+ # else
+ branches.map(&:name)
+ # end
+ # end
rescue GRPC::BadStatus => e
raise CommandError.new(e)
end
@@ -127,13 +135,15 @@ module Gitlab
# Returns an Array of tag names
def tag_names
- Gitlab::GitalyClient.migrate(:tag_names) do |is_enabled|
- if is_enabled
- gitaly_ref_client.tag_names
- else
- rugged.tags.map { |t| t.name }
- end
- end
+ # Gitlab::GitalyClient.migrate(:tag_names) do |is_enabled|
+ # NOTE: This feature is intentionally disabled until
+ # https://gitlab.com/gitlab-org/gitaly/issues/179 is resolved
+ # if is_enabled
+ # gitaly_ref_client.tag_names
+ # else
+ rugged.tags.map { |t| t.name }
+ # end
+ # end
rescue GRPC::BadStatus => e
raise CommandError.new(e)
end
@@ -454,17 +464,19 @@ module Gitlab
# Returns a RefName for a given SHA
def ref_name_for_sha(ref_path, sha)
- Gitlab::GitalyClient.migrate(:find_ref_name) do |is_enabled|
- if is_enabled
- gitaly_ref_client.find_ref_name(sha, ref_path)
- else
- args = %W(#{Gitlab.config.git.bin_path} for-each-ref --count=1 #{ref_path} --contains #{sha})
+ # NOTE: This feature is intentionally disabled until
+ # https://gitlab.com/gitlab-org/gitaly/issues/180 is resolved
+ # Gitlab::GitalyClient.migrate(:find_ref_name) do |is_enabled|
+ # if is_enabled
+ # gitaly_ref_client.find_ref_name(sha, ref_path)
+ # else
+ args = %W(#{Gitlab.config.git.bin_path} for-each-ref --count=1 #{ref_path} --contains #{sha})
- # Not found -> ["", 0]
- # Found -> ["b8d95eb4969eefacb0a58f6a28f6803f8070e7b9 commit\trefs/environments/production/77\n", 0]
- Gitlab::Popen.popen(args, @path).first.split.last
- end
- end
+ # Not found -> ["", 0]
+ # Found -> ["b8d95eb4969eefacb0a58f6a28f6803f8070e7b9 commit\trefs/environments/production/77\n", 0]
+ Gitlab::Popen.popen(args, @path).first.split.last
+ # end
+ # end
end
# Returns commits collection
@@ -978,6 +990,10 @@ module Gitlab
private
+ def alternate_object_directories
+ Gitlab::Git::Env.all.values_at(*ALLOWED_OBJECT_DIRECTORIES_VARIABLES).compact
+ end
+
# Get the content of a blob for a given commit. If the blob is a commit
# (for submodules) then return the blob's OID.
def blob_content(commit, blob_name)
diff --git a/lib/gitlab/git/rev_list.rb b/lib/gitlab/git/rev_list.rb
index 79dd0cf7df2..a16b0ed76f4 100644
--- a/lib/gitlab/git/rev_list.rb
+++ b/lib/gitlab/git/rev_list.rb
@@ -1,41 +1,42 @@
module Gitlab
module Git
class RevList
- attr_reader :project, :env
-
- ALLOWED_VARIABLES = %w[GIT_OBJECT_DIRECTORY GIT_ALTERNATE_OBJECT_DIRECTORIES].freeze
-
- def initialize(oldrev, newrev, project:, env: nil)
- @project = project
- @env = env.presence || {}
- @args = [Gitlab.config.git.bin_path,
- "--git-dir=#{project.repository.path_to_repo}",
- "rev-list",
- "--max-count=1",
- oldrev,
- "^#{newrev}"]
+ attr_reader :oldrev, :newrev, :path_to_repo
+
+ def initialize(path_to_repo:, newrev:, oldrev: nil)
+ @oldrev = oldrev
+ @newrev = newrev
+ @path_to_repo = path_to_repo
end
- def execute
- Gitlab::Popen.popen(@args, nil, parse_environment_variables)
+ # This method returns an array of new references
+ def new_refs
+ execute([*base_args, newrev, '--not', '--all'])
end
- def valid?
- environment_variables.all? do |(name, value)|
- value.to_s.start_with?(project.repository.path_to_repo)
- end
+ # This methods returns an array of missed references
+ def missed_ref
+ execute([*base_args, '--max-count=1', oldrev, "^#{newrev}"])
end
private
- def parse_environment_variables
- return {} unless valid?
+ def execute(args)
+ output, status = Gitlab::Popen.popen(args, nil, Gitlab::Git::Env.all.stringify_keys)
+
+ unless status.zero?
+ raise "Got a non-zero exit code while calling out `#{args.join(' ')}`."
+ end
- environment_variables
+ output.split("\n")
end
- def environment_variables
- @environment_variables ||= env.slice(*ALLOWED_VARIABLES).compact
+ def base_args
+ [
+ Gitlab.config.git.bin_path,
+ "--git-dir=#{path_to_repo}",
+ 'rev-list'
+ ]
end
end
end
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index eea2f206902..99724db8da2 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -18,13 +18,12 @@ module Gitlab
attr_reader :actor, :project, :protocol, :user_access, :authentication_abilities
- def initialize(actor, project, protocol, authentication_abilities:, env: {})
+ def initialize(actor, project, protocol, authentication_abilities:)
@actor = actor
@project = project
@protocol = protocol
@authentication_abilities = authentication_abilities
@user_access = UserAccess.new(user, project: project)
- @env = env
end
def check(cmd, changes)
@@ -152,7 +151,6 @@ module Gitlab
change,
user_access: user_access,
project: project,
- env: @env,
skip_authorization: deploy_key?,
protocol: protocol
).exec
diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb
index 71811be6f50..4a54e7ef2e7 100644
--- a/lib/gitlab/import_export/relation_factory.rb
+++ b/lib/gitlab/import_export/relation_factory.rb
@@ -186,7 +186,7 @@ module Gitlab
end
def admin_user?
- @user.is_admin?
+ @user.admin?
end
def parsed_relation_hash
diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb
index f98481c6d3a..6e42d8941fb 100644
--- a/lib/gitlab/o_auth/user.rb
+++ b/lib/gitlab/o_auth/user.rb
@@ -148,7 +148,7 @@ module Gitlab
def build_new_user
user_params = user_attributes.merge(extern_uid: auth_hash.uid, provider: auth_hash.provider, skip_confirmation: true)
- Users::CreateService.new(nil, user_params).build
+ Users::BuildService.new(nil, user_params).execute
end
def user_attributes
diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb
index 8f1d1fdc02e..2e31f4462f9 100644
--- a/lib/gitlab/visibility_level.rb
+++ b/lib/gitlab/visibility_level.rb
@@ -63,7 +63,7 @@ module Gitlab
end
def allowed_for?(user, level)
- user.is_admin? || allowed_level?(level.to_i)
+ user.admin? || allowed_level?(level.to_i)
end
# Return true if the specified level is allowed for the current user.
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index a9a48f7188f..f41c73154f5 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -431,8 +431,7 @@ namespace :gitlab do
def check_repo_base_user_and_group
gitlab_shell_ssh_user = Gitlab.config.gitlab_shell.ssh_user
- gitlab_shell_owner_group = Gitlab.config.gitlab_shell.owner_group
- puts "Repo paths owned by #{gitlab_shell_ssh_user}:#{gitlab_shell_owner_group}?"
+ puts "Repo paths owned by #{gitlab_shell_ssh_user}:root, or #{gitlab_shell_ssh_user}:#{Gitlab.config.gitlab_shell.owner_group}?"
Gitlab.config.repositories.storages.each do |name, repository_storage|
repo_base_path = repository_storage['path']
@@ -443,15 +442,16 @@ namespace :gitlab do
break
end
- uid = uid_for(gitlab_shell_ssh_user)
- gid = gid_for(gitlab_shell_owner_group)
- if File.stat(repo_base_path).uid == uid && File.stat(repo_base_path).gid == gid
+ user_id = uid_for(gitlab_shell_ssh_user)
+ root_group_id = gid_for('root')
+ group_ids = [root_group_id, gid_for(Gitlab.config.gitlab_shell.owner_group)]
+ if File.stat(repo_base_path).uid == user_id && group_ids.include?(File.stat(repo_base_path).gid)
puts "yes".color(:green)
else
puts "no".color(:red)
- puts " User id for #{gitlab_shell_ssh_user}: #{uid}. Groupd id for #{gitlab_shell_owner_group}: #{gid}".color(:blue)
+ puts " User id for #{gitlab_shell_ssh_user}: #{user_id}. Groupd id for root: #{root_group_id}".color(:blue)
try_fixing_it(
- "sudo chown -R #{gitlab_shell_ssh_user}:#{gitlab_shell_owner_group} #{repo_base_path}"
+ "sudo chown -R #{gitlab_shell_ssh_user}:root #{repo_base_path}"
)
for_more_information(
see_installation_guide_section "GitLab Shell"
diff --git a/lib/tasks/gitlab/gitaly.rake b/lib/tasks/gitlab/gitaly.rake
index 9f6cfe3957c..8079c6e416c 100644
--- a/lib/tasks/gitlab/gitaly.rake
+++ b/lib/tasks/gitlab/gitaly.rake
@@ -7,10 +7,10 @@ namespace :gitlab do
abort %(Please specify the directory where you want to install gitaly:\n rake "gitlab:gitaly:install[/home/git/gitaly]")
end
- tag = "v#{Gitlab::GitalyClient.expected_server_version}"
+ version = Gitlab::GitalyClient.expected_server_version
repo = 'https://gitlab.com/gitlab-org/gitaly.git'
- checkout_or_clone_tag(tag: tag, repo: repo, target_dir: args.dir)
+ checkout_or_clone_version(version: version, repo: repo, target_dir: args.dir)
_, status = Gitlab::Popen.popen(%w[which gmake])
command = status.zero? ? 'gmake' : 'make'
diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake
index dd2fda54e62..95687066819 100644
--- a/lib/tasks/gitlab/shell.rake
+++ b/lib/tasks/gitlab/shell.rake
@@ -1,19 +1,18 @@
namespace :gitlab do
namespace :shell do
desc "GitLab | Install or upgrade gitlab-shell"
- task :install, [:tag, :repo] => :environment do |t, args|
+ task :install, [:repo] => :environment do |t, args|
warn_user_is_not_gitlab
default_version = Gitlab::Shell.version_required
- default_version_tag = "v#{default_version}"
- args.with_defaults(tag: default_version_tag, repo: 'https://gitlab.com/gitlab-org/gitlab-shell.git')
+ args.with_defaults(repo: 'https://gitlab.com/gitlab-org/gitlab-shell.git')
gitlab_url = Gitlab.config.gitlab.url
# gitlab-shell requires a / at the end of the url
gitlab_url += '/' unless gitlab_url.end_with?('/')
target_dir = Gitlab.config.gitlab_shell.path
- checkout_or_clone_tag(tag: default_version_tag, repo: args.repo, target_dir: target_dir)
+ checkout_or_clone_version(version: default_version, repo: args.repo, target_dir: target_dir)
# Make sure we're on the right tag
Dir.chdir(target_dir) do
diff --git a/lib/tasks/gitlab/task_helpers.rb b/lib/tasks/gitlab/task_helpers.rb
index cdba2262bc2..e3c9d3b491c 100644
--- a/lib/tasks/gitlab/task_helpers.rb
+++ b/lib/tasks/gitlab/task_helpers.rb
@@ -147,41 +147,30 @@ module Gitlab
Rails.env.test? ? Rails.root.join('tmp/tests') : Gitlab.config.gitlab.user_home
end
- def checkout_or_clone_tag(tag:, repo:, target_dir:)
- if Dir.exist?(target_dir)
- checkout_tag(tag, target_dir)
- else
- clone_repo(repo, target_dir)
- end
+ def checkout_or_clone_version(version:, repo:, target_dir:)
+ version =
+ if version.starts_with?("=")
+ version.sub(/\A=/, '') # tag or branch
+ else
+ "v#{version}" # tag
+ end
- reset_to_tag(tag, target_dir)
+ clone_repo(repo, target_dir) unless Dir.exist?(target_dir)
+ checkout_version(version, target_dir)
+ reset_to_version(version, target_dir)
end
def clone_repo(repo, target_dir)
run_command!(%W[#{Gitlab.config.git.bin_path} clone -- #{repo} #{target_dir}])
end
- def checkout_tag(tag, target_dir)
- run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} fetch --tags --quiet])
- run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} checkout --quiet #{tag}])
+ def checkout_version(version, target_dir)
+ run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} fetch --quiet])
+ run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} checkout --quiet #{version}])
end
- 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
-
- if tag
- run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} reset --hard #{tag.strip}])
- else
- raise Gitlab::TaskFailedError
- end
+ def reset_to_version(version, target_dir)
+ run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} reset --hard #{version}])
end
end
end
diff --git a/lib/tasks/gitlab/update_templates.rake b/lib/tasks/gitlab/update_templates.rake
index dbdfb335a5c..cb2adc81c9d 100644
--- a/lib/tasks/gitlab/update_templates.rake
+++ b/lib/tasks/gitlab/update_templates.rake
@@ -44,7 +44,7 @@ namespace :gitlab do
),
Template.new(
"https://gitlab.com/gitlab-org/gitlab-ci-yml.git",
- /(\.{1,2}|LICENSE|Pages|autodeploy|\.gitlab-ci.yml)\z/
+ /(\.{1,2}|LICENSE|CONTRIBUTING.md|Pages|autodeploy|\.gitlab-ci.yml)\z/
)
].freeze
diff --git a/lib/tasks/gitlab/workhorse.rake b/lib/tasks/gitlab/workhorse.rake
index baea94bf8ca..a00b02188cf 100644
--- a/lib/tasks/gitlab/workhorse.rake
+++ b/lib/tasks/gitlab/workhorse.rake
@@ -7,10 +7,10 @@ namespace :gitlab do
abort %(Please specify the directory where you want to install gitlab-workhorse:\n rake "gitlab:workhorse:install[/home/git/gitlab-workhorse]")
end
- tag = "v#{Gitlab::Workhorse.version}"
+ version = Gitlab::Workhorse.version
repo = 'https://gitlab.com/gitlab-org/gitlab-workhorse.git'
- checkout_or_clone_tag(tag: tag, repo: repo, target_dir: args.dir)
+ checkout_or_clone_version(version: version, repo: repo, target_dir: args.dir)
_, status = Gitlab::Popen.popen(%w[which gmake])
command = status.zero? ? 'gmake' : 'make'
diff --git a/lib/tasks/import.rake b/lib/tasks/import.rake
index 15131fbf755..a9dad6a1bf0 100644
--- a/lib/tasks/import.rake
+++ b/lib/tasks/import.rake
@@ -52,7 +52,6 @@ class NewImporter < ::Gitlab::GithubImport::Importer
project.repository.add_remote(project.import_type, project.import_url)
project.repository.set_remote_as_mirror(project.import_type)
project.repository.fetch_remote(project.import_type, forced: true)
- project.repository.remove_remote(project.import_type)
rescue => e
# Expire cache to prevent scenarios such as:
# 1. First import failed, but the repo was imported successfully, so +exists?+ returns true
diff --git a/package.json b/package.json
index 6d4f99e33b3..a17399ddb8f 100644
--- a/package.json
+++ b/package.json
@@ -1,10 +1,10 @@
{
"private": true,
"scripts": {
- "dev-server": "webpack-dev-server --config config/webpack.config.js",
- "eslint": "eslint --max-warnings 0 --ext .js .",
- "eslint-fix": "eslint --max-warnings 0 --ext .js --fix .",
- "eslint-report": "eslint --max-warnings 0 --ext .js --format html --output-file ./eslint-report.html .",
+ "dev-server": "nodemon --watch config/webpack.config.js -- ./node_modules/.bin/webpack-dev-server --config config/webpack.config.js",
+ "eslint": "eslint --max-warnings 0 --ext .js,.vue .",
+ "eslint-fix": "eslint --max-warnings 0 --ext .js,.vue --fix .",
+ "eslint-report": "eslint --max-warnings 0 --ext .js,.vue --format html --output-file ./eslint-report.html .",
"karma": "karma start config/karma.config.js --single-run",
"karma-coverage": "BABEL_ENV=coverage karma start config/karma.config.js --single-run",
"karma-start": "karma start config/karma.config.js",
@@ -20,10 +20,12 @@
"bootstrap-sass": "^3.3.6",
"compression-webpack-plugin": "^0.3.2",
"core-js": "^2.4.1",
+ "css-loader": "^0.28.0",
"d3": "^3.5.11",
"document-register-element": "^1.3.0",
"dropzone": "^4.2.0",
"emoji-unicode-version": "^0.2.1",
+ "eslint-plugin-html": "^2.0.1",
"file-loader": "^0.11.1",
"jquery": "^2.2.1",
"jquery-ujs": "^1.2.1",
@@ -34,6 +36,7 @@
"pikaday": "^1.5.1",
"raphael": "^2.2.7",
"raw-loader": "^0.5.1",
+ "react-dev-utils": "^0.5.2",
"select2": "3.5.2-browserify",
"stats-webpack-plugin": "^0.4.3",
"three": "^0.84.0",
@@ -42,8 +45,10 @@
"timeago.js": "^2.0.5",
"underscore": "^1.8.3",
"visibilityjs": "^1.2.4",
- "vue": "^2.2.4",
+ "vue": "^2.2.6",
+ "vue-loader": "^11.3.4",
"vue-resource": "^0.9.3",
+ "vue-template-compiler": "^2.2.6",
"webpack": "^2.3.3",
"webpack-bundle-analyzer": "^2.3.0"
},
@@ -65,6 +70,7 @@
"karma-phantomjs-launcher": "^1.0.2",
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^2.0.2",
+ "nodemon": "^1.11.0",
"webpack-dev-server": "^2.4.2"
}
}
diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb
index 45db7a92fa4..7ce4e9009f5 100644
--- a/qa/qa/page/main/menu.rb
+++ b/qa/qa/page/main/menu.rb
@@ -11,7 +11,7 @@ module QA
end
def go_to_admin_area
- within_user_menu { click_link 'Admin Area' }
+ within_user_menu { click_link 'Admin area' }
end
def sign_out
diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh
index 6e3f76b8399..6cacb81b8bc 100755
--- a/scripts/prepare_build.sh
+++ b/scripts/prepare_build.sh
@@ -15,21 +15,12 @@ retry() {
return 1
}
-if [ -f /.dockerenv ] || [ -f ./dockerinit ]; then
- cp config/database.yml.mysql config/database.yml
- sed -i 's/username:.*/username: root/g' config/database.yml
- sed -i 's/password:.*/password:/g' config/database.yml
- sed -i 's/# socket:.*/host: mysql/g' config/database.yml
+cp config/database.yml.mysql config/database.yml
+sed -i 's/username:.*/username: root/g' config/database.yml
+sed -i 's/password:.*/password:/g' config/database.yml
+sed -i 's/# socket:.*/host: mysql/g' config/database.yml
- cp config/resque.yml.example config/resque.yml
- sed -i 's/localhost/redis/g' config/resque.yml
+cp config/resque.yml.example config/resque.yml
+sed -i 's/localhost/redis/g' config/resque.yml
- export FLAGS="--path vendor --retry 3 --quiet"
-else
- rnd=$(awk 'BEGIN { srand() ; printf("%d\n",rand()*5) }')
- export PATH="$HOME/bin:/usr/local/bin:/usr/bin:/bin"
- cp config/database.yml.mysql config/database.yml
- sed "s/username\:.*$/username\: runner/" -i config/database.yml
- sed "s/password\:.*$/password\: 'password'/" -i config/database.yml
- sed "s/gitlabhq_test/gitlabhq_test_$rnd/" -i config/database.yml
-fi
+export FLAGS="--path vendor --retry 3 --quiet"
diff --git a/spec/controllers/projects/builds_controller_spec.rb b/spec/controllers/projects/builds_controller_spec.rb
index 13208d21918..faf3770f5e9 100644
--- a/spec/controllers/projects/builds_controller_spec.rb
+++ b/spec/controllers/projects/builds_controller_spec.rb
@@ -60,7 +60,7 @@ describe Projects::BuildsController do
expect(json_response['text']).to eq status.text
expect(json_response['label']).to eq status.label
expect(json_response['icon']).to eq status.icon
- expect(json_response['favicon']).to eq status.favicon
+ expect(json_response['favicon']).to eq "/assets/ci_favicons/#{status.favicon}.ico"
end
end
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 1739d40ab88..cc393bd24f2 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -1208,7 +1208,7 @@ describe Projects::MergeRequestsController do
expect(json_response['text']).to eq status.text
expect(json_response['label']).to eq status.label
expect(json_response['icon']).to eq status.icon
- expect(json_response['favicon']).to eq status.favicon
+ expect(json_response['favicon']).to eq "/assets/ci_favicons/#{status.favicon}.ico"
end
end
diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb
index d8f9bfd0d37..d9192177a06 100644
--- a/spec/controllers/projects/pipelines_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_controller_spec.rb
@@ -86,7 +86,7 @@ describe Projects::PipelinesController do
expect(json_response['text']).to eq status.text
expect(json_response['label']).to eq status.label
expect(json_response['icon']).to eq status.icon
- expect(json_response['favicon']).to eq status.favicon
+ expect(json_response['favicon']).to eq "/assets/ci_favicons/#{status.favicon}.ico"
end
end
end
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index 90c35e2c7f8..93f4903119c 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -40,6 +40,7 @@ FactoryGirl.define do
transient do
line_number 14
+ diff_refs { noteable.try(:diff_refs) }
end
position do
@@ -48,7 +49,7 @@ FactoryGirl.define do
new_path: "files/ruby/popen.rb",
old_line: nil,
new_line: line_number,
- diff_refs: noteable.try(:diff_refs)
+ diff_refs: diff_refs
)
end
diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb
index 56d5c7c327e..d5f595894d6 100644
--- a/spec/features/admin/admin_groups_spec.rb
+++ b/spec/features/admin/admin_groups_spec.rb
@@ -25,13 +25,22 @@ feature 'Admin Groups', feature: true do
visit admin_groups_path
click_link "New group"
- fill_in 'group_path', with: 'gitlab'
- fill_in 'group_description', with: 'Group description'
+ path_component = 'gitlab'
+ group_name = 'GitLab group name'
+ group_description = 'Description of group for GitLab'
+ fill_in 'group_path', with: path_component
+ fill_in 'group_name', with: group_name
+ fill_in 'group_description', with: group_description
click_button "Create group"
- expect(current_path).to eq admin_group_path(Group.find_by(path: 'gitlab'))
- expect(page).to have_content('Group: gitlab')
- expect(page).to have_content('Group description')
+ expect(current_path).to eq admin_group_path(Group.find_by(path: path_component))
+ content = page.find('div#content-body')
+ h3_texts = content.all('h3').collect(&:text).join("\n")
+ expect(h3_texts).to match group_name
+ li_texts = content.all('li').collect(&:text).join("\n")
+ expect(li_texts).to match group_name
+ expect(li_texts).to match path_component
+ expect(li_texts).to match group_description
end
scenario 'shows the visibility level radio populated with the default value' do
@@ -39,6 +48,15 @@ feature 'Admin Groups', feature: true do
expect_selected_visibility(internal)
end
+
+ scenario 'when entered in group path, it auto filled the group name', js: true do
+ visit admin_groups_path
+ click_link "New group"
+ group_path = 'gitlab'
+ fill_in 'group_path', with: group_path
+ name_field = find('input#group_name')
+ expect(name_field.value).to eq group_path
+ end
end
describe 'show a group' do
@@ -59,6 +77,17 @@ feature 'Admin Groups', feature: true do
expect_selected_visibility(group.visibility_level)
end
+
+ scenario 'edit group path does not change group name', js: true do
+ group = create(:group, :private)
+
+ visit admin_group_edit_path(group)
+ name_field = find('input#group_name')
+ original_name = name_field.value
+ fill_in 'group_path', with: 'this-new-path'
+
+ expect(name_field.value).to eq original_name
+ end
end
describe 'add user into a group', js: true do
diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index c0807b8c507..f6c3bc6a58d 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -223,7 +223,7 @@ describe "Admin::Users", feature: true do
it "changes user entry" do
user.reload
expect(user.name).to eq('Big Bang')
- expect(user.is_admin?).to be_truthy
+ expect(user.admin?).to be_truthy
expect(user.password_expires_at).to be <= Time.now
end
end
diff --git a/spec/features/dashboard/group_spec.rb b/spec/features/dashboard/group_spec.rb
index 19f5d1b0f30..1d4b86ed4b4 100644
--- a/spec/features/dashboard/group_spec.rb
+++ b/spec/features/dashboard/group_spec.rb
@@ -5,16 +5,18 @@ RSpec.describe 'Dashboard Group', feature: true do
login_as(:user)
end
- it 'creates new grpup' do
+ it 'creates new group', js: true do
visit dashboard_groups_path
click_link 'New group'
+ new_path = 'Samurai'
+ new_description = 'Tokugawa Shogunate'
- fill_in 'group_path', with: 'Samurai'
- fill_in 'group_description', with: 'Tokugawa Shogunate'
+ fill_in 'group_path', with: new_path
+ fill_in 'group_description', with: new_description
click_button 'Create group'
- expect(current_path).to eq group_path(Group.find_by(name: 'Samurai'))
- expect(page).to have_content('Samurai')
- expect(page).to have_content('Tokugawa Shogunate')
+ expect(current_path).to eq group_path(Group.find_by(name: new_path))
+ expect(page).to have_content(new_path)
+ expect(page).to have_content(new_description)
end
end
diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb
index fa5524e18d8..4c9adcabe34 100644
--- a/spec/features/dashboard/shortcuts_spec.rb
+++ b/spec/features/dashboard/shortcuts_spec.rb
@@ -1,27 +1,49 @@
require 'spec_helper'
feature 'Dashboard shortcuts', feature: true, js: true do
- before do
- login_as :user
- visit root_dashboard_path
- end
+ context 'logged in' do
+ before do
+ login_as :user
+ visit root_dashboard_path
+ end
+
+ scenario 'Navigate to tabs' do
+ find('body').native.send_keys([:shift, 'P'])
+
+ check_page_title('Projects')
+
+ find('body').native.send_key([:shift, 'I'])
+
+ check_page_title('Issues')
- scenario 'Navigate to tabs' do
- find('body').native.send_keys([:shift, 'P'])
+ find('body').native.send_key([:shift, 'M'])
+
+ check_page_title('Merge Requests')
+
+ find('body').native.send_keys([:shift, 'T'])
+
+ check_page_title('Todos')
+ end
+ end
- check_page_title('Projects')
+ context 'logged out' do
+ before do
+ visit explore_root_path
+ end
- find('body').native.send_key([:shift, 'I'])
+ scenario 'Navigate to tabs' do
+ find('body').native.send_keys([:shift, 'P'])
- check_page_title('Issues')
+ expect(page).to have_content('No projects found')
- find('body').native.send_key([:shift, 'M'])
+ find('body').native.send_keys([:shift, 'G'])
- check_page_title('Merge Requests')
+ expect(page).to have_content('No public groups')
- find('body').native.send_keys([:shift, 'T'])
+ find('body').native.send_keys([:shift, 'S'])
- check_page_title('Todos')
+ expect(page).to have_selector('.snippets-list-holder')
+ end
end
def check_page_title(title)
diff --git a/spec/features/discussion_comments/commit_spec.rb b/spec/features/discussion_comments/commit_spec.rb
new file mode 100644
index 00000000000..96e0b78f6b9
--- /dev/null
+++ b/spec/features/discussion_comments/commit_spec.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+
+describe 'Discussion Comments Merge Request', :feature, :js do
+ include RepoHelpers
+
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:merge_request) { create(:merge_request, source_project: project) }
+
+ before do
+ project.add_master(user)
+ login_as(user)
+
+ visit namespace_project_commit_path(project.namespace, project, sample_commit.id)
+ end
+
+ it_behaves_like 'discussion comments', 'commit'
+end
diff --git a/spec/features/discussion_comments/issue_spec.rb b/spec/features/discussion_comments/issue_spec.rb
new file mode 100644
index 00000000000..ccc9efccd18
--- /dev/null
+++ b/spec/features/discussion_comments/issue_spec.rb
@@ -0,0 +1,16 @@
+require 'spec_helper'
+
+describe 'Discussion Comments Issue', :feature, :js do
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project) }
+ let(:issue) { create(:issue, project: project) }
+
+ before do
+ project.add_master(user)
+ login_as(user)
+
+ visit namespace_project_issue_path(project.namespace, project, issue)
+ end
+
+ it_behaves_like 'discussion comments', 'issue'
+end
diff --git a/spec/features/discussion_comments/merge_request_spec.rb b/spec/features/discussion_comments/merge_request_spec.rb
new file mode 100644
index 00000000000..f99ebeb9cd9
--- /dev/null
+++ b/spec/features/discussion_comments/merge_request_spec.rb
@@ -0,0 +1,16 @@
+require 'spec_helper'
+
+describe 'Discussion Comments Merge Request', :feature, :js do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:merge_request) { create(:merge_request, source_project: project) }
+
+ before do
+ project.add_master(user)
+ login_as(user)
+
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ end
+
+ it_behaves_like 'discussion comments', 'merge request'
+end
diff --git a/spec/features/discussion_comments/snippets_spec.rb b/spec/features/discussion_comments/snippets_spec.rb
new file mode 100644
index 00000000000..19a306511b2
--- /dev/null
+++ b/spec/features/discussion_comments/snippets_spec.rb
@@ -0,0 +1,16 @@
+require 'spec_helper'
+
+describe 'Discussion Comments Issue', :feature, :js do
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project) }
+ let(:snippet) { create(:project_snippet, :private, project: project, author: user) }
+
+ before do
+ project.add_master(user)
+ login_as(user)
+
+ visit namespace_project_snippet_path(project.namespace, project, snippet)
+ end
+
+ it_behaves_like 'discussion comments', 'snippet'
+end
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
index 7670c4caea4..3d32c47bf09 100644
--- a/spec/features/groups_spec.rb
+++ b/spec/features/groups_spec.rb
@@ -83,7 +83,7 @@ feature 'Group', feature: true do
end
end
- describe 'create a nested group' do
+ describe 'create a nested group', js: true do
let(:group) { create(:group, path: 'foo') }
context 'as admin' do
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index e3213d24f6a..362d167befa 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -601,10 +601,10 @@ 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
+ it "doesn't add double newline to end of a single attachment markdown" do
dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif')
- expect(page.find_field("issue_description").value).to match /\n\n$/
+ expect(page.find_field("issue_description").value).not_to match /\n\n$/
end
end
diff --git a/spec/features/merge_requests/diff_notes_resolve_spec.rb b/spec/features/merge_requests/diff_notes_resolve_spec.rb
index 88d28b649a4..0e23c3a8849 100644
--- a/spec/features/merge_requests/diff_notes_resolve_spec.rb
+++ b/spec/features/merge_requests/diff_notes_resolve_spec.rb
@@ -198,6 +198,8 @@ feature 'Diff notes resolve', feature: true, js: true do
it 'does not mark discussion as resolved when resolving single note' do
page.first '.diff-content .note' do
first('.line-resolve-btn').click
+
+ expect(page).to have_selector('.note-action-button .loading')
expect(first('.line-resolve-btn')['data-original-title']).to eq("Resolved by #{user.name}")
end
diff --git a/spec/features/merge_requests/diff_notes_spec.rb b/spec/features/merge_requests/diff_notes_spec.rb
deleted file mode 100644
index 06fad1007e8..00000000000
--- a/spec/features/merge_requests/diff_notes_spec.rb
+++ /dev/null
@@ -1,238 +0,0 @@
-require 'spec_helper'
-
-feature 'Diff notes', js: true, feature: true do
- include WaitForAjax
-
- before do
- login_as :admin
- @merge_request = create(:merge_request)
- @project = @merge_request.source_project
- end
-
- context 'merge request diffs' do
- let(:comment_button_class) { '.add-diff-note' }
- let(:notes_holder_input_class) { 'js-temp-notes-holder' }
- let(:notes_holder_input_xpath) { './following-sibling::*[contains(concat(" ", @class, " "), " notes_holder ")]' }
- let(:test_note_comment) { 'this is a test note!' }
-
- context 'when hovering over a parallel view diff file' do
- before(:each) do
- visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, view: 'parallel')
- end
-
- context 'with an old line on the left and no line on the right' do
- it 'should allow commenting on the left side' do
- should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_23_22"]').find(:xpath, '..'), 'left')
- end
-
- it 'should not allow commenting on the right side' do
- should_not_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_23_22"]').find(:xpath, '..'), 'right')
- end
- end
-
- context 'with no line on the left and a new line on the right' do
- it 'should not allow commenting on the left side' do
- should_not_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15"]').find(:xpath, '..'), 'left')
- end
-
- it 'should allow commenting on the right side' do
- should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15"]').find(:xpath, '..'), 'right')
- end
- end
-
- context 'with an old line on the left and a new line on the right' do
- it 'should allow commenting on the left side' do
- should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"]').find(:xpath, '..'), 'left')
- end
-
- it 'should allow commenting on the right side' do
- should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"]').find(:xpath, '..'), 'right')
- end
- end
-
- context 'with an unchanged line on the left and an unchanged line on the right' do
- it 'should allow commenting on the left side' do
- should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]', match: :first).find(:xpath, '..'), 'left')
- end
-
- it 'should allow commenting on the right side' do
- should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]', match: :first).find(:xpath, '..'), 'right')
- end
- end
-
- context 'with a match line' do
- it 'should not allow commenting on the left side' do
- should_not_allow_commenting(find('.match', match: :first).find(:xpath, '..'), 'left')
- end
-
- it 'should not allow commenting on the right side' do
- should_not_allow_commenting(find('.match', match: :first).find(:xpath, '..'), 'right')
- end
- end
-
- context 'with an unfolded line' do
- before(:each) do
- find('.js-unfold', match: :first).click
- wait_for_ajax
- end
-
- # The first `.js-unfold` unfolds upwards, therefore the first
- # `.line_holder` will be an unfolded line.
- let(:line_holder) { first('.line_holder[id="1"]') }
-
- it 'should not allow commenting on the left side' do
- should_not_allow_commenting(line_holder, 'left')
- end
-
- it 'should not allow commenting on the right side' do
- should_not_allow_commenting(line_holder, 'right')
- end
- end
- end
-
- context 'when hovering over an inline view diff file' do
- before do
- visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, view: 'inline')
- end
-
- context 'with a new line' do
- it 'should allow commenting' do
- should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'))
- end
- end
-
- context 'with an old line' do
- it 'should allow commenting' do
- should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]'))
- end
- end
-
- context 'with an unchanged line' do
- it 'should allow commenting' do
- should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]'))
- end
- end
-
- context 'with a match line' do
- it 'should not allow commenting' do
- should_not_allow_commenting(find('.match', match: :first))
- end
- end
-
- context 'with an unfolded line' do
- before(:each) do
- find('.js-unfold', match: :first).click
- wait_for_ajax
- end
-
- # The first `.js-unfold` unfolds upwards, therefore the first
- # `.line_holder` will be an unfolded line.
- let(:line_holder) { first('.line_holder[id="1"]') }
-
- it 'should not allow commenting' do
- should_not_allow_commenting line_holder
- end
- end
-
- context 'when hovering over a diff discussion' do
- before do
- visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, view: 'inline')
- should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]'))
- visit namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
- end
-
- it 'should not allow commenting' do
- should_not_allow_commenting(find('.line_holder', match: :first))
- end
- end
- end
-
- context 'when the MR only supports legacy diff notes' do
- before do
- @merge_request.merge_request_diff.update_attributes(start_commit_sha: nil)
- visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, view: 'inline')
- end
-
- context 'with a new line' do
- it 'should allow commenting' do
- should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'))
- end
- end
-
- context 'with an old line' do
- it 'should allow commenting' do
- should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]'))
- end
- end
-
- context 'with an unchanged line' do
- it 'should allow commenting' do
- should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]'))
- end
- end
-
- context 'with a match line' do
- it 'should not allow commenting' do
- should_not_allow_commenting(find('.match', match: :first))
- end
- end
- end
-
- def should_allow_commenting(line_holder, diff_side = nil)
- line = get_line_components(line_holder, diff_side)
- line[:content].hover
- expect(line[:num]).to have_css comment_button_class
-
- comment_on_line(line_holder, line)
-
- assert_comment_persistence(line_holder)
- end
-
- def should_not_allow_commenting(line_holder, diff_side = nil)
- line = get_line_components(line_holder, diff_side)
- line[:content].hover
- expect(line[:num]).not_to have_css comment_button_class
- end
-
- def get_line_components(line_holder, diff_side = nil)
- if diff_side.nil?
- get_inline_line_components(line_holder)
- else
- get_parallel_line_components(line_holder, diff_side)
- end
- end
-
- def get_inline_line_components(line_holder)
- { content: line_holder.find('.line_content', match: :first), num: line_holder.find('.diff-line-num', match: :first) }
- end
-
- def get_parallel_line_components(line_holder, diff_side = nil)
- side_index = diff_side == 'left' ? 0 : 1
- # Wait for `.line_content`
- line_holder.find('.line_content', match: :first)
- # Wait for `.diff-line-num`
- line_holder.find('.diff-line-num', match: :first)
- { content: line_holder.all('.line_content')[side_index], num: line_holder.all('.diff-line-num')[side_index] }
- end
-
- def comment_on_line(line_holder, line)
- line[:num].find(comment_button_class).trigger 'click'
- line_holder.find(:xpath, notes_holder_input_xpath)
-
- notes_holder_input = line_holder.find(:xpath, notes_holder_input_xpath)
- expect(notes_holder_input[:class]).to include(notes_holder_input_class)
-
- notes_holder_input.fill_in 'note[note]', with: test_note_comment
- click_button 'Comment'
- wait_for_ajax
- end
-
- def assert_comment_persistence(line_holder)
- expect(line_holder).to have_xpath notes_holder_input_xpath
-
- notes_holder_saved = line_holder.find(:xpath, notes_holder_input_xpath)
- expect(notes_holder_saved[:class]).not_to include(notes_holder_input_class)
- expect(notes_holder_saved).to have_content test_note_comment
- end
- end
-end
diff --git a/spec/features/merge_requests/discussion_spec.rb b/spec/features/merge_requests/discussion_spec.rb
new file mode 100644
index 00000000000..f59d0faa274
--- /dev/null
+++ b/spec/features/merge_requests/discussion_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+feature 'Merge Request Discussions', feature: true do
+ before do
+ login_as :admin
+ end
+
+ context "Diff discussions" do
+ let(:merge_request) { create(:merge_request, importing: true) }
+ let(:project) { merge_request.source_project }
+ let!(:old_merge_request_diff) { merge_request.merge_request_diffs.create(diff_refs: outdated_diff_refs) }
+ let!(:new_merge_request_diff) { merge_request.merge_request_diffs.create }
+
+ let!(:outdated_discussion) { create(:diff_note_on_merge_request, project: project, noteable: merge_request, position: outdated_position).to_discussion }
+ let!(:active_discussion) { create(:diff_note_on_merge_request, noteable: merge_request, project: project).to_discussion }
+
+ let(:outdated_position) do
+ Gitlab::Diff::Position.new(
+ old_path: "files/ruby/popen.rb",
+ new_path: "files/ruby/popen.rb",
+ old_line: nil,
+ new_line: 9,
+ diff_refs: outdated_diff_refs
+ )
+ end
+
+ let(:outdated_diff_refs) { project.commit("874797c3a73b60d2187ed6e2fcabd289ff75171e").diff_refs }
+
+ before(:each) do
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ end
+
+ context 'active discussions' do
+ it 'shows a link to the diff' do
+ within(".discussion[data-discussion-id='#{active_discussion.id}']") do
+ path = diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, anchor: active_discussion.line_code)
+ expect(page).to have_link('the diff', href: path)
+ end
+ end
+ end
+
+ context 'outdated discussions' do
+ it 'shows a link to the outdated diff' do
+ within(".discussion[data-discussion-id='#{outdated_discussion.id}']") do
+ path = diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, diff_id: old_merge_request_diff.id, anchor: outdated_discussion.line_code)
+ expect(page).to have_link('an outdated diff', href: path)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
index 646e7bab265..cd540ca113a 100644
--- a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
+++ b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
@@ -89,6 +89,19 @@ feature 'Merge When Pipeline Succeeds', :feature, :js do
it_behaves_like 'Merge when pipeline succeeds activator'
end
end
+
+ describe 'enabling Merge when pipeline succeeds via dropdown' do
+ it 'activates the Merge when pipeline succeeds feature' do
+ click_button 'Select merge moment'
+ within('.js-merge-dropdown') do
+ click_link 'Merge when pipeline succeeds'
+ end
+
+ expect(page).to have_content "Set by #{user.name} to be merged automatically when the pipeline succeeds."
+ expect(page).to have_content "The source branch will not be removed."
+ expect(page).to have_link "Cancel automatic merge"
+ end
+ end
end
context 'when merge when pipeline succeeds is enabled' do
diff --git a/spec/features/merge_requests/user_posts_diff_notes_spec.rb b/spec/features/merge_requests/user_posts_diff_notes_spec.rb
new file mode 100644
index 00000000000..7756202e3f5
--- /dev/null
+++ b/spec/features/merge_requests/user_posts_diff_notes_spec.rb
@@ -0,0 +1,294 @@
+require 'spec_helper'
+
+feature 'Merge requests > User posts diff notes', :js do
+ let(:user) { create(:user) }
+ let(:merge_request) { create(:merge_request) }
+ let(:project) { merge_request.source_project }
+
+ before do
+ project.add_developer(user)
+ login_as(user)
+ end
+
+ let(:comment_button_class) { '.add-diff-note' }
+ let(:notes_holder_input_class) { 'js-temp-notes-holder' }
+ let(:notes_holder_input_xpath) { './following-sibling::*[contains(concat(" ", @class, " "), " notes_holder ")]' }
+ let(:test_note_comment) { 'this is a test note!' }
+
+ context 'when hovering over a parallel view diff file' do
+ before do
+ visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: 'parallel')
+ end
+
+ context 'with an old line on the left and no line on the right' do
+ it 'allows commenting on the left side' do
+ should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_23_22"]').find(:xpath, '..'), 'left')
+ end
+
+ it 'does not allow commenting on the right side' do
+ should_not_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_23_22"]').find(:xpath, '..'), 'right')
+ end
+ end
+
+ context 'with no line on the left and a new line on the right' do
+ it 'does not allow commenting on the left side' do
+ should_not_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15"]').find(:xpath, '..'), 'left')
+ end
+
+ it 'allows commenting on the right side' do
+ should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15"]').find(:xpath, '..'), 'right')
+ end
+ end
+
+ context 'with an old line on the left and a new line on the right' do
+ it 'allows commenting on the left side' do
+ should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"]').find(:xpath, '..'), 'left')
+ end
+
+ it 'allows commenting on the right side' do
+ should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"]').find(:xpath, '..'), 'right')
+ end
+ end
+
+ context 'with an unchanged line on the left and an unchanged line on the right' do
+ it 'allows commenting on the left side' do
+ should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]', match: :first).find(:xpath, '..'), 'left')
+ end
+
+ it 'allows commenting on the right side' do
+ should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]', match: :first).find(:xpath, '..'), 'right')
+ end
+ end
+
+ context 'with a match line' do
+ it 'does not allow commenting on the left side' do
+ should_not_allow_commenting(find('.match', match: :first).find(:xpath, '..'), 'left')
+ end
+
+ it 'does not allow commenting on the right side' do
+ should_not_allow_commenting(find('.match', match: :first).find(:xpath, '..'), 'right')
+ end
+ end
+
+ context 'with an unfolded line' do
+ before(:each) do
+ find('.js-unfold', match: :first).click
+ wait_for_ajax
+ end
+
+ # The first `.js-unfold` unfolds upwards, therefore the first
+ # `.line_holder` will be an unfolded line.
+ let(:line_holder) { first('.line_holder[id="1"]') }
+
+ it 'does not allow commenting on the left side' do
+ should_not_allow_commenting(line_holder, 'left')
+ end
+
+ it 'does not allow commenting on the right side' do
+ should_not_allow_commenting(line_holder, 'right')
+ end
+ end
+ end
+
+ context 'when hovering over an inline view diff file' do
+ before do
+ visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: 'inline')
+ end
+
+ context 'with a new line' do
+ it 'allows commenting' do
+ should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'))
+ end
+ end
+
+ context 'with an old line' do
+ it 'allows commenting' do
+ should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]'))
+ end
+ end
+
+ context 'with an unchanged line' do
+ it 'allows commenting' do
+ should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]'))
+ end
+ end
+
+ context 'with a match line' do
+ it 'does not allow commenting' do
+ should_not_allow_commenting(find('.match', match: :first))
+ end
+ end
+
+ context 'with an unfolded line' do
+ before(:each) do
+ find('.js-unfold', match: :first).click
+ wait_for_ajax
+ end
+
+ # The first `.js-unfold` unfolds upwards, therefore the first
+ # `.line_holder` will be an unfolded line.
+ let(:line_holder) { first('.line_holder[id="1"]') }
+
+ it 'does not allow commenting' do
+ should_not_allow_commenting line_holder
+ end
+ end
+
+ context 'when hovering over a diff discussion' do
+ before do
+ visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: 'inline')
+ should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]'))
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ end
+
+ it 'does not allow commenting' do
+ should_not_allow_commenting(find('.line_holder', match: :first))
+ end
+ end
+ end
+
+ context 'when cancelling the comment addition' do
+ before do
+ visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: 'inline')
+ end
+
+ context 'with a new line' do
+ it 'allows dismissing a comment' do
+ should_allow_dismissing_a_comment(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'))
+ end
+ end
+ end
+
+ describe 'with muliple note forms' do
+ before do
+ visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: 'inline')
+ click_diff_line(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'))
+ click_diff_line(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]'))
+ end
+
+ describe 'posting a note' do
+ it 'adds as discussion' do
+ expect(page).to have_css('.js-temp-notes-holder', count: 2)
+
+ should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]'), asset_form_reset: false)
+ expect(page).to have_css('.notes_holder .note', count: 1)
+ expect(page).to have_css('.js-temp-notes-holder', count: 1)
+ expect(page).to have_button('Reply...')
+ end
+ end
+ end
+
+ context 'when the MR only supports legacy diff notes' do
+ before do
+ merge_request.merge_request_diff.update_attributes(start_commit_sha: nil)
+ visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: 'inline')
+ end
+
+ context 'with a new line' do
+ it 'allows commenting' do
+ should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'))
+ end
+ end
+
+ context 'with an old line' do
+ it 'allows commenting' do
+ should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]'))
+ end
+ end
+
+ context 'with an unchanged line' do
+ it 'allows commenting' do
+ should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]'))
+ end
+ end
+
+ context 'with a match line' do
+ it 'does not allow commenting' do
+ should_not_allow_commenting(find('.match', match: :first))
+ end
+ end
+ end
+
+ def should_allow_commenting(line_holder, diff_side = nil, asset_form_reset: true)
+ write_comment_on_line(line_holder, diff_side)
+
+ click_button 'Comment'
+ wait_for_ajax
+
+ assert_comment_persistence(line_holder, asset_form_reset: asset_form_reset)
+ end
+
+ def should_allow_dismissing_a_comment(line_holder, diff_side = nil)
+ write_comment_on_line(line_holder, diff_side)
+
+ find('.js-close-discussion-note-form').trigger('click')
+
+ assert_comment_dismissal(line_holder)
+ end
+
+ def should_not_allow_commenting(line_holder, diff_side = nil)
+ line = get_line_components(line_holder, diff_side)
+ line[:content].hover
+ expect(line[:num]).not_to have_css comment_button_class
+ end
+
+ def get_line_components(line_holder, diff_side = nil)
+ if diff_side.nil?
+ get_inline_line_components(line_holder)
+ else
+ get_parallel_line_components(line_holder, diff_side)
+ end
+ end
+
+ def get_inline_line_components(line_holder)
+ { content: line_holder.find('.line_content', match: :first), num: line_holder.find('.diff-line-num', match: :first) }
+ end
+
+ def get_parallel_line_components(line_holder, diff_side = nil)
+ side_index = diff_side == 'left' ? 0 : 1
+ # Wait for `.line_content`
+ line_holder.find('.line_content', match: :first)
+ # Wait for `.diff-line-num`
+ line_holder.find('.diff-line-num', match: :first)
+ { content: line_holder.all('.line_content')[side_index], num: line_holder.all('.diff-line-num')[side_index] }
+ end
+
+ def click_diff_line(line_holder, diff_side = nil)
+ line = get_line_components(line_holder, diff_side)
+ line[:content].hover
+
+ expect(line[:num]).to have_css comment_button_class
+
+ line[:num].find(comment_button_class).trigger 'click'
+ end
+
+ def write_comment_on_line(line_holder, diff_side)
+ click_diff_line(line_holder, diff_side)
+
+ notes_holder_input = line_holder.find(:xpath, notes_holder_input_xpath)
+
+ expect(notes_holder_input[:class]).to include(notes_holder_input_class)
+
+ notes_holder_input.fill_in 'note[note]', with: test_note_comment
+ end
+
+ def assert_comment_persistence(line_holder, asset_form_reset:)
+ notes_holder_saved = line_holder.find(:xpath, notes_holder_input_xpath)
+
+ expect(notes_holder_saved[:class]).not_to include(notes_holder_input_class)
+ expect(notes_holder_saved).to have_content test_note_comment
+
+ assert_form_is_reset if asset_form_reset
+ end
+
+ def assert_comment_dismissal(line_holder)
+ expect(line_holder).not_to have_xpath notes_holder_input_xpath
+ expect(page).not_to have_content test_note_comment
+
+ assert_form_is_reset
+ end
+
+ def assert_form_is_reset
+ expect(page).to have_no_css('.js-temp-notes-holder')
+ end
+end
diff --git a/spec/features/merge_requests/user_posts_notes.rb b/spec/features/merge_requests/user_posts_notes.rb
new file mode 100644
index 00000000000..c7cc4d6bc72
--- /dev/null
+++ b/spec/features/merge_requests/user_posts_notes.rb
@@ -0,0 +1,145 @@
+require 'spec_helper'
+
+describe 'Merge requests > User posts notes', :js do
+ let(:project) { create(:project) }
+ let(:merge_request) do
+ create(:merge_request, source_project: project, target_project: project)
+ end
+ let!(:note) do
+ create(:note_on_merge_request, :with_attachment, noteable: merge_request,
+ project: project)
+ end
+
+ before do
+ login_as :admin
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ end
+
+ subject { page }
+
+ describe 'the note form' do
+ it 'is valid' do
+ is_expected.to have_css('.js-main-target-form', visible: true, count: 1)
+ expect(find('.js-main-target-form .js-comment-button').value).
+ to eq('Comment')
+ page.within('.js-main-target-form') do
+ expect(page).not_to have_link('Cancel')
+ end
+ end
+
+ describe 'with text' do
+ before do
+ page.within('.js-main-target-form') do
+ fill_in 'note[note]', with: 'This is awesome'
+ end
+ end
+
+ it 'has enable submit button and preview button' do
+ page.within('.js-main-target-form') do
+ expect(page).not_to have_css('.js-comment-button[disabled]')
+ expect(page).to have_css('.js-md-preview-button', visible: true)
+ end
+ end
+ end
+ end
+
+ describe 'when posting a note' do
+ before do
+ page.within('.js-main-target-form') do
+ fill_in 'note[note]', with: 'This is awesome!'
+ find('.js-md-preview-button').click
+ click_button 'Comment'
+ end
+ end
+
+ it 'is added and form reset' do
+ is_expected.to have_content('This is awesome!')
+ page.within('.js-main-target-form') do
+ expect(page).to have_no_field('note[note]', with: 'This is awesome!')
+ expect(page).to have_css('.js-md-preview', visible: :hidden)
+ end
+ page.within('.js-main-target-form') do
+ is_expected.to have_css('.js-note-text', visible: true)
+ end
+ end
+ end
+
+ describe 'when editing a note' do
+ it 'there should be a hidden edit form' do
+ is_expected.to have_css('.note-edit-form:not(.mr-note-edit-form)', visible: false, count: 1)
+ is_expected.to have_css('.note-edit-form.mr-note-edit-form', visible: false, count: 1)
+ end
+
+ describe 'editing the note' do
+ before do
+ find('.note').hover
+ find('.js-note-edit').click
+ end
+
+ it 'shows the note edit form and hide the note body' do
+ page.within("#note_#{note.id}") do
+ expect(find('.current-note-edit-form', visible: true)).to be_visible
+ expect(find('.note-edit-form', visible: true)).to be_visible
+ expect(find(:css, '.note-body > .note-text', visible: false)).not_to be_visible
+ end
+ end
+
+ it 'resets the edit note form textarea with the original content of the note if cancelled' do
+ within('.current-note-edit-form') do
+ fill_in 'note[note]', with: 'Some new content'
+ find('.btn-cancel').click
+ expect(find('.js-note-text', visible: false).text).to eq ''
+ end
+ end
+
+ it 'allows using markdown buttons after saving a note and then trying to edit it again' do
+ page.within('.current-note-edit-form') do
+ fill_in 'note[note]', with: 'This is the new content'
+ find('.btn-save').click
+ end
+
+ find('.note').hover
+ find('.js-note-edit').click
+
+ page.within('.current-note-edit-form') do
+ expect(find('#note_note').value).to eq('This is the new content')
+ find('.js-md:first-child').click
+ expect(find('#note_note').value).to eq('This is the new content****')
+ end
+ end
+
+ it 'appends the edited at time to the note' do
+ page.within('.current-note-edit-form') do
+ fill_in 'note[note]', with: 'Some new content'
+ find('.btn-save').click
+ end
+
+ page.within("#note_#{note.id}") do
+ is_expected.to have_css('.note_edited_ago')
+ expect(find('.note_edited_ago').text).
+ to match(/less than a minute ago/)
+ end
+ end
+ end
+
+ describe 'deleting an attachment' do
+ before do
+ find('.note').hover
+ find('.js-note-edit').click
+ end
+
+ it 'shows the delete link' do
+ page.within('.note-attachment') do
+ is_expected.to have_css('.js-note-attachment-delete')
+ end
+ end
+
+ it 'removes the attachment div and resets the edit form' do
+ find('.js-note-attachment-delete').click
+ is_expected.not_to have_css('.note-attachment')
+ is_expected.not_to have_css('.current-note-edit-form')
+ wait_for_ajax
+ end
+ end
+ end
+end
diff --git a/spec/features/merge_requests/user_sees_system_notes_spec.rb b/spec/features/merge_requests/user_sees_system_notes_spec.rb
new file mode 100644
index 00000000000..55d0f9d728c
--- /dev/null
+++ b/spec/features/merge_requests/user_sees_system_notes_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+
+feature 'Merge requests > User sees system notes' do
+ let(:public_project) { create(:project, :public) }
+ let(:private_project) { create(:project, :private) }
+ let(:issue) { create(:issue, project: private_project) }
+ let(:merge_request) { create(:merge_request, source_project: public_project, source_branch: 'markdown') }
+ let!(:note) { create(:note_on_merge_request, :system, noteable: merge_request, project: public_project, note: "mentioned in #{issue.to_reference(public_project)}") }
+
+ context 'when logged-in as a member of the private project' do
+ before do
+ user = create(:user)
+ private_project.add_developer(user)
+ login_as(user)
+ end
+
+ it 'shows the system note' do
+ visit namespace_project_merge_request_path(public_project.namespace, public_project, merge_request)
+
+ expect(page).to have_css('.system-note')
+ end
+ end
+
+ context 'when not logged-in' do
+ it 'hides the system note' do
+ visit namespace_project_merge_request_path(public_project.namespace, public_project, merge_request)
+
+ expect(page).not_to have_css('.system-note')
+ end
+ end
+end
diff --git a/spec/features/merge_requests/merge_request_versions_spec.rb b/spec/features/merge_requests/versions_spec.rb
index 04e85ed3f73..68a68f5d3f3 100644
--- a/spec/features/merge_requests/merge_request_versions_spec.rb
+++ b/spec/features/merge_requests/versions_spec.rb
@@ -36,8 +36,23 @@ feature 'Merge Request versions', js: true, feature: true do
expect(page).to have_content '5 changed files'
end
- it 'show the message about disabled comments' do
- expect(page).to have_content 'Comments are disabled'
+ it 'show the message about disabled comment creation' do
+ expect(page).to have_content 'comment creation is disabled'
+ end
+
+ it 'shows comments that were last relevant at that version' do
+ position = Gitlab::Diff::Position.new(
+ old_path: ".gitmodules",
+ new_path: ".gitmodules",
+ old_line: nil,
+ new_line: 4,
+ diff_refs: merge_request_diff1.diff_refs
+ )
+ outdated_diff_note = create(:diff_note_on_merge_request, project: project, noteable: merge_request, position: position)
+ outdated_diff_note.position = outdated_diff_note.original_position
+ outdated_diff_note.save!
+
+ expect(page).to have_css(".diffs .notes[data-discussion-id='#{outdated_diff_note.discussion_id}']")
end
end
diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb
deleted file mode 100644
index 783f2e93909..00000000000
--- a/spec/features/notes_on_merge_requests_spec.rb
+++ /dev/null
@@ -1,285 +0,0 @@
-require 'spec_helper'
-
-describe 'Comments', feature: true do
- include RepoHelpers
- include WaitForAjax
-
- describe 'On a merge request', js: true, feature: true do
- let!(:project) { create(:project) }
- let!(:merge_request) do
- create(:merge_request, source_project: project, target_project: project)
- end
-
- let!(:note) do
- create(:note_on_merge_request, :with_attachment, noteable: merge_request,
- project: project)
- end
-
- before do
- login_as :admin
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
- end
-
- subject { page }
-
- describe 'the note form' do
- it 'is valid' do
- is_expected.to have_css('.js-main-target-form', visible: true, count: 1)
- expect(find('.js-main-target-form .js-comment-button').value).
- to eq('Comment')
- page.within('.js-main-target-form') do
- expect(page).not_to have_link('Cancel')
- end
- end
-
- describe 'with text' do
- before do
- page.within('.js-main-target-form') do
- fill_in 'note[note]', with: 'This is awesome'
- end
- end
-
- it 'has enable submit button and preview button' do
- page.within('.js-main-target-form') do
- expect(page).not_to have_css('.js-comment-button[disabled]')
- expect(page).to have_css('.js-md-preview-button', visible: true)
- end
- end
- end
- end
-
- describe 'when posting a note' do
- before do
- page.within('.js-main-target-form') do
- fill_in 'note[note]', with: 'This is awsome!'
- find('.js-md-preview-button').click
- click_button 'Comment'
- end
- end
-
- it 'is added and form reset' do
- is_expected.to have_content('This is awsome!')
- page.within('.js-main-target-form') do
- expect(page).to have_no_field('note[note]', with: 'This is awesome!')
- expect(page).to have_css('.js-md-preview', visible: :hidden)
- end
- page.within('.js-main-target-form') do
- is_expected.to have_css('.js-note-text', visible: true)
- end
- end
- end
-
- describe 'when editing a note', js: true do
- it 'there should be a hidden edit form' do
- is_expected.to have_css('.note-edit-form:not(.mr-note-edit-form)', visible: false, count: 1)
- is_expected.to have_css('.note-edit-form.mr-note-edit-form', visible: false, count: 1)
- end
-
- describe 'editing the note' do
- before do
- find('.note').hover
- find('.js-note-edit').click
- end
-
- it 'shows the note edit form and hide the note body' do
- page.within("#note_#{note.id}") do
- expect(find('.current-note-edit-form', visible: true)).to be_visible
- expect(find('.note-edit-form', visible: true)).to be_visible
- expect(find(:css, '.note-body > .note-text', visible: false)).not_to be_visible
- end
- end
-
- it 'resets the edit note form textarea with the original content of the note if cancelled' do
- within('.current-note-edit-form') do
- fill_in 'note[note]', with: 'Some new content'
- find('.btn-cancel').click
- expect(find('.js-note-text', visible: false).text).to eq ''
- end
- end
-
- it 'allows using markdown buttons after saving a note and then trying to edit it again' do
- page.within('.current-note-edit-form') do
- fill_in 'note[note]', with: 'This is the new content'
- find('.btn-save').click
- end
-
- find('.note').hover
- find('.js-note-edit').click
-
- page.within('.current-note-edit-form') do
- expect(find('#note_note').value).to eq('This is the new content')
- find('.js-md:first-child').click
- expect(find('#note_note').value).to eq('This is the new content****')
- end
- end
-
- it 'appends the edited at time to the note' do
- page.within('.current-note-edit-form') do
- fill_in 'note[note]', with: 'Some new content'
- find('.btn-save').click
- end
-
- page.within("#note_#{note.id}") do
- is_expected.to have_css('.note_edited_ago')
- expect(find('.note_edited_ago').text).
- to match(/less than a minute ago/)
- end
- end
- end
-
- describe 'deleting an attachment' do
- before do
- find('.note').hover
- find('.js-note-edit').click
- end
-
- it 'shows the delete link' do
- page.within('.note-attachment') do
- is_expected.to have_css('.js-note-attachment-delete')
- end
- end
-
- it 'removes the attachment div and resets the edit form' do
- find('.js-note-attachment-delete').click
- is_expected.not_to have_css('.note-attachment')
- is_expected.not_to have_css('.current-note-edit-form')
- wait_for_ajax
- end
- end
- end
- end
-
- describe 'Handles cross-project system notes', js: true, feature: true do
- let(:user) { create(:user) }
- let(:project) { create(:project, :public) }
- let(:project2) { create(:project, :private) }
- let(:issue) { create(:issue, project: project2) }
- let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'markdown') }
- let!(:note) { create(:note_on_merge_request, :system, noteable: merge_request, project: project, note: "mentioned in #{issue.to_reference(project)}") }
-
- it 'shows the system note' do
- login_as :admin
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
-
- expect(page).to have_css('.system-note')
- end
-
- it 'hides redacted system note' do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
-
- expect(page).not_to have_css('.system-note')
- end
- end
-
- describe 'On a merge request diff', js: true, feature: true do
- let(:merge_request) { create(:merge_request) }
- let(:project) { merge_request.source_project }
-
- before do
- login_as :admin
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
- end
-
- subject { page }
-
- describe 'when adding a note' do
- before do
- click_diff_line
- end
-
- describe 'the notes holder' do
- it { is_expected.to have_css('.js-temp-notes-holder') }
-
- it 'has .new_note css class' do
- page.within('.js-temp-notes-holder') do
- expect(subject).to have_css('.new-note')
- end
- end
- end
-
- describe 'the note form' do
- it "does not add a second form for same row" do
- click_diff_line
-
- is_expected.
- to have_css("form[data-line-code='#{line_code}']",
- count: 1)
- end
-
- it 'is removed when canceled' do
- is_expected.to have_css('.js-temp-notes-holder')
-
- page.within("form[data-line-code='#{line_code}']") do
- find('.js-close-discussion-note-form').trigger('click')
- end
-
- is_expected.to have_no_css('.js-temp-notes-holder')
- end
- end
- end
-
- describe 'with muliple note forms' do
- before do
- click_diff_line
- click_diff_line(line_code_2)
- end
-
- it { is_expected.to have_css('.js-temp-notes-holder', count: 2) }
-
- describe 'previewing them separately' do
- before do
- # add two separate texts and trigger previews on both
- page.within("tr[id='#{line_code}'] + .js-temp-notes-holder") do
- fill_in 'note[note]', with: 'One comment on line 7'
- find('.js-md-preview-button').click
- end
- page.within("tr[id='#{line_code_2}'] + .js-temp-notes-holder") do
- fill_in 'note[note]', with: 'Another comment on line 10'
- find('.js-md-preview-button').click
- end
- end
- end
-
- describe 'posting a note' do
- before do
- page.within("tr[id='#{line_code_2}'] + .js-temp-notes-holder") do
- fill_in 'note[note]', with: 'Another comment on line 10'
- click_button('Comment')
- end
- end
-
- it 'adds as discussion' do
- is_expected.to have_content('Another comment on line 10')
- is_expected.to have_css('.notes_holder')
- is_expected.to have_css('.notes_holder .note', count: 1)
- is_expected.to have_button('Reply...')
- end
-
- it 'adds code to discussion' do
- click_button 'Reply...'
-
- page.within(first('.js-discussion-note-form')) do
- fill_in 'note[note]', with: '```{{ test }}```'
-
- click_button('Comment')
- end
-
- expect(page).to have_content('{{ test }}')
- end
- end
- end
- end
-
- def line_code
- sample_compare.changes.first[:line_code]
- end
-
- def line_code_2
- sample_compare.changes.last[:line_code]
- end
-
- def click_diff_line(data = line_code)
- find(".line_holder[id='#{data}'] td.line_content").hover
- find(".line_holder[id='#{data}'] button").trigger('click')
- end
-end
diff --git a/spec/features/projects/wiki/shortcuts_spec.rb b/spec/features/projects/wiki/shortcuts_spec.rb
new file mode 100644
index 00000000000..c1f6b0cce3b
--- /dev/null
+++ b/spec/features/projects/wiki/shortcuts_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+feature 'Wiki shortcuts', :feature, :js do
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project, namespace: user.namespace) }
+ let(:wiki_page) do
+ WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute
+ end
+
+ before do
+ login_as(user)
+ visit namespace_project_wiki_path(project.namespace, project, wiki_page)
+ end
+
+ scenario 'Visit edit wiki page using "e" keyboard shortcut' do
+ find('body').native.send_key('e')
+
+ expect(find('.wiki-page-title')).to have_content('Edit Page')
+ end
+end
diff --git a/spec/fixtures/trace/ansi-sequence-and-unicode b/spec/fixtures/trace/ansi-sequence-and-unicode
new file mode 100644
index 00000000000..5d2466f0d0f
--- /dev/null
+++ b/spec/fixtures/trace/ansi-sequence-and-unicode
@@ -0,0 +1,5 @@
+.
+..
+😺
+ヾ(´༎ຶД༎ຶ`)ノ
+許功蓋
diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb
index f3e79cc7290..2c0e9975f73 100644
--- a/spec/helpers/preferences_helper_spec.rb
+++ b/spec/helpers/preferences_helper_spec.rb
@@ -86,10 +86,10 @@ describe PreferencesHelper do
context 'when repository is not empty' do
let(:project) { create(:project, :public, :repository) }
- it 'returns readme if user has repository access' do
+ it 'returns files and readme if user has repository access' do
allow(helper).to receive(:can?).with(nil, :download_code, project).and_return(true)
- expect(helper.default_project_view).to eq('readme')
+ expect(helper.default_project_view).to eq('files')
end
it 'returns activity if user does not have repository access' do
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 40efab6e4f7..a7fc5d14859 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -265,4 +265,27 @@ describe ProjectsHelper do
end
end
end
+
+ describe "#visibility_select_options" do
+ let(:project) { create(:project, :repository) }
+ let(:user) { create(:user) }
+
+ before do
+ allow(helper).to receive(:current_user).and_return(user)
+
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
+ end
+
+ it "does not include the Public restricted level" do
+ expect(helper.send(:visibility_select_options, project, Gitlab::VisibilityLevel::PRIVATE)).not_to include('Public')
+ end
+
+ it "includes the Internal level" do
+ expect(helper.send(:visibility_select_options, project, Gitlab::VisibilityLevel::PRIVATE)).to include('Internal')
+ end
+
+ it "includes the Private level" do
+ expect(helper.send(:visibility_select_options, project, Gitlab::VisibilityLevel::PRIVATE)).to include('Private')
+ end
+ end
end
diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js
index ea7753c7a1d..68ad5f66676 100644
--- a/spec/javascripts/awards_handler_spec.js
+++ b/spec/javascripts/awards_handler_spec.js
@@ -3,6 +3,8 @@
import Cookies from 'js-cookie';
import AwardsHandler from '~/awards_handler';
+require('~/lib/utils/common_utils');
+
(function() {
var awardsHandler, lazyAssert, urlRoot, openAndWaitForEmojiMenu;
@@ -28,7 +30,7 @@ import AwardsHandler from '~/awards_handler';
loadFixtures('issues/issue_with_comment.html.raw');
awardsHandler = new AwardsHandler;
spyOn(awardsHandler, 'postEmoji').and.callFake((function(_this) {
- return function(url, emoji, cb) {
+ return function(button, url, emoji, cb) {
return cb();
};
})(this));
@@ -63,7 +65,7 @@ import AwardsHandler from '~/awards_handler';
$emojiMenu = $('.emoji-menu');
expect($emojiMenu.length).toBe(1);
expect($emojiMenu.hasClass('is-visible')).toBe(true);
- expect($emojiMenu.find('#emoji_search').length).toBe(1);
+ expect($emojiMenu.find('.js-emoji-menu-search').length).toBe(1);
return expect($('.js-awards-block.current').length).toBe(1);
});
});
@@ -115,6 +117,27 @@ import AwardsHandler from '~/awards_handler';
return expect($emojiButton.next('.js-counter').text()).toBe('4');
});
});
+ describe('::userAuthored', function() {
+ it('should update tooltip to user authored title', function() {
+ var $thumbsUpEmoji, $votesBlock;
+ $votesBlock = $('.js-awards-block').eq(0);
+ $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent();
+ $thumbsUpEmoji.attr('data-title', 'sam');
+ awardsHandler.userAuthored($thumbsUpEmoji);
+ return expect($thumbsUpEmoji.data("original-title")).toBe("You cannot vote on your own issue, MR and note");
+ });
+ it('should restore tooltip back to initial vote list', function() {
+ var $thumbsUpEmoji, $votesBlock;
+ jasmine.clock().install();
+ $votesBlock = $('.js-awards-block').eq(0);
+ $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent();
+ $thumbsUpEmoji.attr('data-title', 'sam');
+ awardsHandler.userAuthored($thumbsUpEmoji);
+ jasmine.clock().tick(2801);
+ jasmine.clock().uninstall();
+ return expect($thumbsUpEmoji.data("original-title")).toBe("sam");
+ });
+ });
describe('::getAwardUrl', function() {
return it('returns the url for request', function() {
return expect(awardsHandler.getAwardUrl()).toBe('http://test.host/frontend-fixtures/issues-project/issues/1/toggle_award_emoji');
@@ -194,16 +217,35 @@ import AwardsHandler from '~/awards_handler';
return expect($thumbsUpEmoji.data("original-title")).toBe('sam');
});
});
- describe('search', function() {
- return it('should filter the emoji', function(done) {
+ describe('::searchEmojis', () => {
+ it('should filter the emoji', function(done) {
return openAndWaitForEmojiMenu()
.then(() => {
expect($('[data-name=angel]').is(':visible')).toBe(true);
expect($('[data-name=anger]').is(':visible')).toBe(true);
- $('#emoji_search').val('ali').trigger('input');
+ awardsHandler.searchEmojis('ali');
expect($('[data-name=angel]').is(':visible')).toBe(false);
expect($('[data-name=anger]').is(':visible')).toBe(false);
expect($('[data-name=alien]').is(':visible')).toBe(true);
+ expect($('.js-emoji-menu-search').val()).toBe('ali');
+ })
+ .then(done)
+ .catch((err) => {
+ done.fail(`Failed to open and build emoji menu: ${err.message}`);
+ });
+ });
+ it('should clear the search when searching for nothing', function(done) {
+ return openAndWaitForEmojiMenu()
+ .then(() => {
+ awardsHandler.searchEmojis('ali');
+ expect($('[data-name=angel]').is(':visible')).toBe(false);
+ expect($('[data-name=anger]').is(':visible')).toBe(false);
+ expect($('[data-name=alien]').is(':visible')).toBe(true);
+ awardsHandler.searchEmojis('');
+ expect($('[data-name=angel]').is(':visible')).toBe(true);
+ expect($('[data-name=anger]').is(':visible')).toBe(true);
+ expect($('[data-name=alien]').is(':visible')).toBe(true);
+ expect($('.js-emoji-menu-search').val()).toBe('');
})
.then(done)
.catch((err) => {
@@ -211,6 +253,7 @@ import AwardsHandler from '~/awards_handler';
});
});
});
+
describe('emoji menu', function() {
const emojiSelector = '[data-name="sunglasses"]';
const openEmojiMenuAndAddEmoji = function() {
diff --git a/spec/javascripts/boards/list_spec.js b/spec/javascripts/boards/list_spec.js
index a9d4c6ef76f..24a2da9f6b6 100644
--- a/spec/javascripts/boards/list_spec.js
+++ b/spec/javascripts/boards/list_spec.js
@@ -107,4 +107,44 @@ describe('List model', () => {
expect(gl.boardService.moveIssue)
.toHaveBeenCalledWith(issue.id, list.id, listDup.id, undefined, undefined);
});
+
+ describe('page number', () => {
+ beforeEach(() => {
+ spyOn(list, 'getIssues');
+ });
+
+ it('increase page number if current issue count is more than the page size', () => {
+ for (let i = 0; i < 30; i += 1) {
+ list.issues.push(new ListIssue({
+ title: 'Testing',
+ iid: _.random(10000) + i,
+ confidential: false,
+ labels: [list.label]
+ }));
+ }
+ list.issuesSize = 50;
+
+ expect(list.issues.length).toBe(30);
+
+ list.nextPage();
+
+ expect(list.page).toBe(2);
+ expect(list.getIssues).toHaveBeenCalled();
+ });
+
+ it('does not increase page number if issue count is less than the page size', () => {
+ list.issues.push(new ListIssue({
+ title: 'Testing',
+ iid: _.random(10000),
+ confidential: false,
+ labels: [list.label]
+ }));
+ list.issuesSize = 2;
+
+ list.nextPage();
+
+ expect(list.page).toBe(1);
+ expect(list.getIssues).toHaveBeenCalled();
+ });
+ });
});
diff --git a/spec/javascripts/build_spec.js b/spec/javascripts/build_spec.js
index edd4b3c1440..8ec96bdb583 100644
--- a/spec/javascripts/build_spec.js
+++ b/spec/javascripts/build_spec.js
@@ -1,11 +1,11 @@
/* eslint-disable no-new */
/* global Build */
-
-require('~/lib/utils/datetime_utility');
-require('~/lib/utils/url_utility');
-require('~/build');
-require('~/breakpoints');
-require('vendor/jquery.nicescroll');
+import { bytesToKiB } from '~/lib/utils/number_utils';
+import '~/lib/utils/datetime_utility';
+import '~/lib/utils/url_utility';
+import '~/build';
+import '~/breakpoints';
+import 'vendor/jquery.nicescroll';
describe('Build', () => {
const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/builds/1`;
@@ -98,9 +98,9 @@ describe('Build', () => {
jasmine.clock().tick(4001);
- expect($.ajax.calls.count()).toBe(2);
+ expect($.ajax.calls.count()).toBe(3);
- args = $.ajax.calls.argsFor(1)[0];
+ args = $.ajax.calls.argsFor(2)[0];
expect(args.url).toBe(`${BUILD_URL}/trace.json`);
expect(args.dataType).toBe('json');
expect(args.data.state).toBe('newstate');
@@ -133,7 +133,7 @@ describe('Build', () => {
expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
jasmine.clock().tick(4001);
- args = $.ajax.calls.argsFor(1)[0];
+ args = $.ajax.calls.argsFor(2)[0];
args.success.call($, {
html: '<span>Different</span>',
status: 'running',
@@ -144,24 +144,6 @@ describe('Build', () => {
expect($('#build-trace .js-build-output').text()).toMatch(/Different/);
});
- it('shows information about truncated log', () => {
- jasmine.clock().tick(4001);
- const [{ success }] = $.ajax.calls.argsFor(0);
-
- success.call($, {
- html: '<span>Update</span>',
- status: 'success',
- append: false,
- truncated: true,
- size: '50',
- });
-
- expect(
- $('#build-trace .js-truncated-info').text().trim(),
- ).toContain('Showing last 50 KiB of log');
- expect($('#build-trace .js-truncated-info-size').text()).toMatch('50');
- });
-
it('reloads the page when the build is done', () => {
spyOn(gl.utils, 'visitUrl');
@@ -176,6 +158,107 @@ describe('Build', () => {
expect(gl.utils.visitUrl).toHaveBeenCalledWith(BUILD_URL);
});
+
+ describe('truncated information', () => {
+ describe('when size is less than total', () => {
+ it('shows information about truncated log', () => {
+ jasmine.clock().tick(4001);
+ const [{ success }] = $.ajax.calls.argsFor(0);
+
+ success.call($, {
+ html: '<span>Update</span>',
+ status: 'success',
+ append: false,
+ size: 50,
+ total: 100,
+ });
+
+ expect(document.querySelector('.js-truncated-info').classList).not.toContain('hidden');
+ });
+
+ it('shows the size in KiB', () => {
+ jasmine.clock().tick(4001);
+ const [{ success }] = $.ajax.calls.argsFor(0);
+ const size = 50;
+
+ success.call($, {
+ html: '<span>Update</span>',
+ status: 'success',
+ append: false,
+ size,
+ total: 100,
+ });
+
+ expect(
+ document.querySelector('.js-truncated-info-size').textContent.trim(),
+ ).toEqual(`${bytesToKiB(size)}`);
+ });
+
+ it('shows incremented size', () => {
+ jasmine.clock().tick(4001);
+ let args = $.ajax.calls.argsFor(0)[0];
+ args.success.call($, {
+ html: '<span>Update</span>',
+ status: 'success',
+ append: false,
+ size: 50,
+ total: 100,
+ });
+
+ expect(
+ document.querySelector('.js-truncated-info-size').textContent.trim(),
+ ).toEqual(`${bytesToKiB(50)}`);
+
+ jasmine.clock().tick(4001);
+ args = $.ajax.calls.argsFor(2)[0];
+ args.success.call($, {
+ html: '<span>Update</span>',
+ status: 'success',
+ append: true,
+ size: 10,
+ total: 100,
+ });
+
+ expect(
+ document.querySelector('.js-truncated-info-size').textContent.trim(),
+ ).toEqual(`${bytesToKiB(60)}`);
+ });
+
+ it('renders the raw link', () => {
+ jasmine.clock().tick(4001);
+ const [{ success }] = $.ajax.calls.argsFor(0);
+
+ success.call($, {
+ html: '<span>Update</span>',
+ status: 'success',
+ append: false,
+ size: 50,
+ total: 100,
+ });
+
+ expect(
+ document.querySelector('.js-raw-link').textContent.trim(),
+ ).toContain('Complete Raw');
+ });
+ });
+
+ describe('when size is equal than total', () => {
+ it('does not show the trunctated information', () => {
+ jasmine.clock().tick(4001);
+ const [{ success }] = $.ajax.calls.argsFor(0);
+
+ success.call($, {
+ html: '<span>Update</span>',
+ status: 'success',
+ append: false,
+ size: 100,
+ total: 100,
+ });
+
+ expect(document.querySelector('.js-truncated-info').classList).toContain('hidden');
+ });
+ });
+ });
});
});
});
diff --git a/spec/javascripts/environments/environment_spec.js b/spec/javascripts/environments/environment_spec.js
index 4431baa4b96..9762688af1a 100644
--- a/spec/javascripts/environments/environment_spec.js
+++ b/spec/javascripts/environments/environment_spec.js
@@ -83,9 +83,10 @@ describe('Environment', () => {
it('should render a table with environments', (done) => {
setTimeout(() => {
+ expect(component.$el.querySelectorAll('table')).toBeDefined();
expect(
- component.$el.querySelectorAll('table tbody tr').length,
- ).toEqual(1);
+ component.$el.querySelector('.environment-name').textContent.trim(),
+ ).toEqual(environment.name);
done();
}, 0);
});
diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js b/spec/javascripts/environments/folder/environments_folder_view_spec.js
index 43a217a67f5..72f3db29a66 100644
--- a/spec/javascripts/environments/folder/environments_folder_view_spec.js
+++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js
@@ -47,9 +47,10 @@ describe('Environments Folder View', () => {
it('should render a table with environments', (done) => {
setTimeout(() => {
+ expect(component.$el.querySelectorAll('table')).toBeDefined();
expect(
- component.$el.querySelectorAll('table tbody tr').length,
- ).toEqual(2);
+ component.$el.querySelector('.environment-name').textContent.trim(),
+ ).toEqual(environmentsList[0].name);
done();
}, 0);
});
diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb
index fddeaaf504d..47d904b865b 100644
--- a/spec/javascripts/fixtures/merge_requests.rb
+++ b/spec/javascripts/fixtures/merge_requests.rb
@@ -7,6 +7,7 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, namespace: namespace, path: 'merge-requests-project') }
let(:merge_request) { create(:merge_request, :with_diffs, source_project: project, target_project: project, description: '- [ ] Task List Item') }
+ let(:merged_merge_request) { create(:merge_request, :merged, source_project: project, target_project: project) }
let(:pipeline) do
create(
:ci_pipeline,
@@ -32,6 +33,12 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
render_merge_request(example.description, merge_request)
end
+ it 'merge_requests/merged_merge_request.html.raw' do |example|
+ allow_any_instance_of(MergeRequest).to receive(:source_branch_exists?).and_return(true)
+ allow_any_instance_of(MergeRequest).to receive(:can_remove_source_branch?).and_return(true)
+ render_merge_request(example.description, merged_merge_request)
+ end
+
private
def render_merge_request(fixture_file_name, merge_request)
diff --git a/spec/javascripts/issue_show/issue_title_spec.js b/spec/javascripts/issue_show/issue_title_spec.js
index 806d728a874..03edbf9f947 100644
--- a/spec/javascripts/issue_show/issue_title_spec.js
+++ b/spec/javascripts/issue_show/issue_title_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import issueTitle from '~/issue_show/issue_title';
+import issueTitle from '~/issue_show/issue_title.vue';
describe('Issue Title', () => {
let IssueTitleComponent;
diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js
index aabc8bea12f..9a2570ef7e9 100644
--- a/spec/javascripts/issue_spec.js
+++ b/spec/javascripts/issue_spec.js
@@ -1,18 +1,17 @@
-/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, no-use-before-define, comma-dangle, max-len */
+/* eslint-disable space-before-function-paren, one-var, one-var-declaration-per-line, no-use-before-define, comma-dangle, max-len */
import Issue from '~/issue';
require('~/lib/utils/text_utility');
describe('Issue', function() {
- var INVALID_URL = 'http://goesnowhere.nothing/whereami';
- var $boxClosed, $boxOpen, $btnClose, $btnReopen;
+ let $boxClosed, $boxOpen, $btnClose, $btnReopen;
preloadFixtures('issues/closed-issue.html.raw');
preloadFixtures('issues/issue-with-task-list.html.raw');
preloadFixtures('issues/open-issue.html.raw');
function expectErrorMessage() {
- var $flashMessage = $('div.flash-alert');
+ const $flashMessage = $('div.flash-alert');
expect($flashMessage).toExist();
expect($flashMessage).toBeVisible();
expect($flashMessage).toHaveText('Unable to update this issue at this time.');
@@ -26,10 +25,28 @@ describe('Issue', function() {
expectVisibility($btnReopen, !isIssueOpen);
}
- function expectPendingRequest(req, $triggeredButton) {
- expect(req.type).toBe('PUT');
- expect(req.url).toBe($triggeredButton.attr('href'));
- expect($triggeredButton).toHaveProp('disabled', true);
+ function expectNewBranchButtonState(isPending, canCreate) {
+ if (Issue.$btnNewBranch.length === 0) {
+ return;
+ }
+
+ const $available = Issue.$btnNewBranch.find('.available');
+ expect($available).toHaveText('New branch');
+
+ if (!isPending && canCreate) {
+ expect($available).toBeVisible();
+ } else {
+ expect($available).toBeHidden();
+ }
+
+ const $unavailable = Issue.$btnNewBranch.find('.unavailable');
+ expect($unavailable).toHaveText('New branch unavailable');
+
+ if (!isPending && !canCreate) {
+ expect($unavailable).toBeVisible();
+ } else {
+ expect($unavailable).toBeHidden();
+ }
}
function expectVisibility($element, shouldBeVisible) {
@@ -81,100 +98,107 @@ describe('Issue', function() {
});
});
- describe('close issue', function() {
- beforeEach(function() {
- loadFixtures('issues/open-issue.html.raw');
- findElements();
- this.issue = new Issue();
-
- expectIssueState(true);
- });
+ [true, false].forEach((isIssueInitiallyOpen) => {
+ describe(`with ${isIssueInitiallyOpen ? 'open' : 'closed'} issue`, function() {
+ const action = isIssueInitiallyOpen ? 'close' : 'reopen';
+
+ function ajaxSpy(req) {
+ if (req.url === this.$triggeredButton.attr('href')) {
+ expect(req.type).toBe('PUT');
+ expect(this.$triggeredButton).toHaveProp('disabled', true);
+ expectNewBranchButtonState(true, false);
+ return this.issueStateDeferred;
+ } else if (req.url === Issue.$btnNewBranch.data('path')) {
+ expect(req.type).toBe('get');
+ expectNewBranchButtonState(true, false);
+ return this.canCreateBranchDeferred;
+ }
+
+ expect(req.url).toBe('unexpected');
+ return null;
+ }
+
+ beforeEach(function() {
+ if (isIssueInitiallyOpen) {
+ loadFixtures('issues/open-issue.html.raw');
+ } else {
+ loadFixtures('issues/closed-issue.html.raw');
+ }
+
+ findElements();
+ this.issue = new Issue();
+ expectIssueState(isIssueInitiallyOpen);
+ this.$triggeredButton = isIssueInitiallyOpen ? $btnClose : $btnReopen;
+
+ this.$projectIssuesCounter = $('.issue_counter');
+ this.$projectIssuesCounter.text('1,001');
+
+ this.issueStateDeferred = new jQuery.Deferred();
+ this.canCreateBranchDeferred = new jQuery.Deferred();
+
+ spyOn(jQuery, 'ajax').and.callFake(ajaxSpy.bind(this));
+ });
- it('closes an issue', function() {
- spyOn(jQuery, 'ajax').and.callFake(function(req) {
- expectPendingRequest(req, $btnClose);
- req.success({
+ it(`${action}s the issue`, function() {
+ this.$triggeredButton.trigger('click');
+ this.issueStateDeferred.resolve({
id: 34
});
- });
-
- $btnClose.trigger('click');
+ this.canCreateBranchDeferred.resolve({
+ can_create_branch: !isIssueInitiallyOpen
+ });
- expectIssueState(false);
- expect($btnClose).toHaveProp('disabled', false);
- expect($('.issue_counter')).toHaveText(0);
- });
+ expectIssueState(!isIssueInitiallyOpen);
+ expect(this.$triggeredButton).toHaveProp('disabled', false);
+ expect(this.$projectIssuesCounter.text()).toBe(isIssueInitiallyOpen ? '1,000' : '1,002');
+ expectNewBranchButtonState(false, !isIssueInitiallyOpen);
+ });
- it('fails to close an issue with success:false', function() {
- spyOn(jQuery, 'ajax').and.callFake(function(req) {
- expectPendingRequest(req, $btnClose);
- req.success({
+ it(`fails to ${action} the issue if saved:false`, function() {
+ this.$triggeredButton.trigger('click');
+ this.issueStateDeferred.resolve({
saved: false
});
- });
-
- $btnClose.attr('href', INVALID_URL);
- $btnClose.trigger('click');
-
- expectIssueState(true);
- expect($btnClose).toHaveProp('disabled', false);
- expectErrorMessage();
- expect($('.issue_counter')).toHaveText(1);
- });
+ this.canCreateBranchDeferred.resolve({
+ can_create_branch: isIssueInitiallyOpen
+ });
- it('fails to closes an issue with HTTP error', function() {
- spyOn(jQuery, 'ajax').and.callFake(function(req) {
- expectPendingRequest(req, $btnClose);
- req.error();
+ expectIssueState(isIssueInitiallyOpen);
+ expect(this.$triggeredButton).toHaveProp('disabled', false);
+ expectErrorMessage();
+ expect(this.$projectIssuesCounter.text()).toBe('1,001');
+ expectNewBranchButtonState(false, isIssueInitiallyOpen);
});
- $btnClose.attr('href', INVALID_URL);
- $btnClose.trigger('click');
-
- expectIssueState(true);
- expect($btnClose).toHaveProp('disabled', true);
- expectErrorMessage();
- expect($('.issue_counter')).toHaveText(1);
- });
-
- it('updates counter', () => {
- spyOn(jQuery, 'ajax').and.callFake(function(req) {
- expectPendingRequest(req, $btnClose);
- req.success({
- id: 34
+ it(`fails to ${action} the issue if HTTP error occurs`, function() {
+ this.$triggeredButton.trigger('click');
+ this.issueStateDeferred.reject();
+ this.canCreateBranchDeferred.resolve({
+ can_create_branch: isIssueInitiallyOpen
});
- });
- expect($('.issue_counter')).toHaveText(1);
- $('.issue_counter').text('1,001');
- expect($('.issue_counter').text()).toEqual('1,001');
- $btnClose.trigger('click');
- expect($('.issue_counter').text()).toEqual('1,000');
- });
- });
+ expectIssueState(isIssueInitiallyOpen);
+ expect(this.$triggeredButton).toHaveProp('disabled', true);
+ expectErrorMessage();
+ expect(this.$projectIssuesCounter.text()).toBe('1,001');
+ expectNewBranchButtonState(false, isIssueInitiallyOpen);
+ });
- describe('reopen issue', function() {
- beforeEach(function() {
- loadFixtures('issues/closed-issue.html.raw');
- findElements();
- this.issue = new Issue();
+ it('disables the new branch button if Ajax call fails', function() {
+ this.$triggeredButton.trigger('click');
+ this.issueStateDeferred.reject();
+ this.canCreateBranchDeferred.reject();
- expectIssueState(false);
- });
-
- it('reopens an issue', function() {
- spyOn(jQuery, 'ajax').and.callFake(function(req) {
- expectPendingRequest(req, $btnReopen);
- req.success({
- id: 34
- });
+ expectNewBranchButtonState(false, false);
});
- $btnReopen.trigger('click');
+ it('does not trigger Ajax call if new branch button is missing', function() {
+ Issue.$btnNewBranch = $();
+ this.canCreateBranchDeferred = null;
- expectIssueState(true);
- expect($btnReopen).toHaveProp('disabled', false);
- expect($('.issue_counter')).toHaveText(1);
+ this.$triggeredButton.trigger('click');
+ this.issueStateDeferred.reject();
+ });
});
});
});
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index 03f3c206f44..56aabc16382 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -313,7 +313,7 @@ require('~/lib/utils/common_utils');
describe('gl.utils.setFavicon', () => {
it('should set page favicon to provided favicon', () => {
- const faviconName = 'custom_favicon';
+ const faviconPath = '//custom_favicon';
const fakeLink = {
setAttribute() {},
};
@@ -321,9 +321,9 @@ require('~/lib/utils/common_utils');
spyOn(window.document, 'getElementById').and.callFake(() => fakeLink);
spyOn(fakeLink, 'setAttribute').and.callFake((attr, val) => {
expect(attr).toEqual('href');
- expect(val.indexOf('/assets/custom_favicon.ico') > -1).toBe(true);
+ expect(val.indexOf(faviconPath) > -1).toBe(true);
});
- gl.utils.setFavicon(faviconName);
+ gl.utils.setFavicon(faviconPath);
});
});
@@ -345,13 +345,12 @@ require('~/lib/utils/common_utils');
describe('gl.utils.setCiStatusFavicon', () => {
it('should set page favicon to CI status favicon based on provided status', () => {
const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/builds/1/status.json`;
- const FAVICON_PATH = 'ci_favicons/';
- const FAVICON = 'icon_status_success';
+ const FAVICON_PATH = '//icon_status_success';
const spySetFavicon = spyOn(gl.utils, 'setFavicon').and.stub();
const spyResetFavicon = spyOn(gl.utils, 'resetFavicon').and.stub();
spyOn($, 'ajax').and.callFake(function (options) {
- options.success({ icon: FAVICON });
- expect(spySetFavicon).toHaveBeenCalledWith(FAVICON_PATH + FAVICON);
+ options.success({ favicon: FAVICON_PATH });
+ expect(spySetFavicon).toHaveBeenCalledWith(FAVICON_PATH);
options.success();
expect(spyResetFavicon).toHaveBeenCalled();
options.error();
diff --git a/spec/javascripts/lib/utils/number_utility_spec.js b/spec/javascripts/lib/utils/number_utility_spec.js
index 5fde8be9123..90b12c9f115 100644
--- a/spec/javascripts/lib/utils/number_utility_spec.js
+++ b/spec/javascripts/lib/utils/number_utility_spec.js
@@ -1,4 +1,4 @@
-import { formatRelevantDigits } from '~/lib/utils/number_utils';
+import { formatRelevantDigits, bytesToKiB } from '~/lib/utils/number_utils';
describe('Number Utils', () => {
describe('formatRelevantDigits', () => {
@@ -38,4 +38,11 @@ describe('Number Utils', () => {
expect(leftFromDecimal.length).toBe(3);
});
});
+
+ describe('bytesToKiB', () => {
+ it('calculates KiB for the given bytes', () => {
+ expect(bytesToKiB(1024)).toEqual(1);
+ expect(bytesToKiB(1000)).toEqual(0.9765625);
+ });
+ });
});
diff --git a/spec/javascripts/lib/utils/poll_spec.js b/spec/javascripts/lib/utils/poll_spec.js
index e3429c2a1cb..918b6d32c43 100644
--- a/spec/javascripts/lib/utils/poll_spec.js
+++ b/spec/javascripts/lib/utils/poll_spec.js
@@ -4,6 +4,20 @@ import Poll from '~/lib/utils/poll';
Vue.use(VueResource);
+const waitForAllCallsToFinish = (service, waitForCount, successCallback) => {
+ const timer = () => {
+ setTimeout(() => {
+ if (service.fetch.calls.count() === waitForCount) {
+ successCallback();
+ } else {
+ timer();
+ }
+ }, 5);
+ };
+
+ timer();
+};
+
class ServiceMock {
constructor(endpoint) {
this.service = Vue.resource(endpoint);
@@ -16,6 +30,7 @@ class ServiceMock {
describe('Poll', () => {
let callbacks;
+ let service;
beforeEach(() => {
callbacks = {
@@ -23,8 +38,11 @@ describe('Poll', () => {
error: () => {},
};
+ service = new ServiceMock('endpoint');
+
spyOn(callbacks, 'success');
spyOn(callbacks, 'error');
+ spyOn(service, 'fetch').and.callThrough();
});
it('calls the success callback when no header for interval is provided', (done) => {
@@ -35,19 +53,20 @@ describe('Poll', () => {
Vue.http.interceptors.push(successInterceptor);
new Poll({
- resource: new ServiceMock('endpoint'),
+ resource: service,
method: 'fetch',
successCallback: callbacks.success,
errorCallback: callbacks.error,
}).makeRequest();
- setTimeout(() => {
+ waitForAllCallsToFinish(service, 1, () => {
expect(callbacks.success).toHaveBeenCalled();
expect(callbacks.error).not.toHaveBeenCalled();
+
+ Vue.http.interceptors = _.without(Vue.http.interceptors, successInterceptor);
+
done();
}, 0);
-
- Vue.http.interceptors = _.without(Vue.http.interceptors, successInterceptor);
});
it('calls the error callback whe the http request returns an error', (done) => {
@@ -58,19 +77,19 @@ describe('Poll', () => {
Vue.http.interceptors.push(errorInterceptor);
new Poll({
- resource: new ServiceMock('endpoint'),
+ resource: service,
method: 'fetch',
successCallback: callbacks.success,
errorCallback: callbacks.error,
}).makeRequest();
- setTimeout(() => {
+ waitForAllCallsToFinish(service, 1, () => {
expect(callbacks.success).not.toHaveBeenCalled();
expect(callbacks.error).toHaveBeenCalled();
- done();
- }, 0);
+ Vue.http.interceptors = _.without(Vue.http.interceptors, errorInterceptor);
- Vue.http.interceptors = _.without(Vue.http.interceptors, errorInterceptor);
+ done();
+ });
});
it('should call the success callback when the interval header is -1', (done) => {
@@ -81,7 +100,7 @@ describe('Poll', () => {
Vue.http.interceptors.push(intervalInterceptor);
new Poll({
- resource: new ServiceMock('endpoint'),
+ resource: service,
method: 'fetch',
successCallback: callbacks.success,
errorCallback: callbacks.error,
@@ -90,10 +109,11 @@ describe('Poll', () => {
setTimeout(() => {
expect(callbacks.success).toHaveBeenCalled();
expect(callbacks.error).not.toHaveBeenCalled();
+
+ Vue.http.interceptors = _.without(Vue.http.interceptors, intervalInterceptor);
+
done();
}, 0);
-
- Vue.http.interceptors = _.without(Vue.http.interceptors, intervalInterceptor);
});
it('starts polling when http status is 200 and interval header is provided', (done) => {
@@ -103,26 +123,28 @@ describe('Poll', () => {
Vue.http.interceptors.push(pollInterceptor);
- const service = new ServiceMock('endpoint');
- spyOn(service, 'fetch').and.callThrough();
-
- new Poll({
+ const Polling = new Poll({
resource: service,
method: 'fetch',
data: { page: 1 },
successCallback: callbacks.success,
errorCallback: callbacks.error,
- }).makeRequest();
+ });
+
+ Polling.makeRequest();
+
+ waitForAllCallsToFinish(service, 2, () => {
+ Polling.stop();
- setTimeout(() => {
expect(service.fetch.calls.count()).toEqual(2);
expect(service.fetch).toHaveBeenCalledWith({ page: 1 });
expect(callbacks.success).toHaveBeenCalled();
expect(callbacks.error).not.toHaveBeenCalled();
- done();
- }, 5);
- Vue.http.interceptors = _.without(Vue.http.interceptors, pollInterceptor);
+ Vue.http.interceptors = _.without(Vue.http.interceptors, pollInterceptor);
+
+ done();
+ });
});
describe('stop', () => {
@@ -133,9 +155,6 @@ describe('Poll', () => {
Vue.http.interceptors.push(pollInterceptor);
- const service = new ServiceMock('endpoint');
- spyOn(service, 'fetch').and.callThrough();
-
const Polling = new Poll({
resource: service,
method: 'fetch',
@@ -150,14 +169,15 @@ describe('Poll', () => {
Polling.makeRequest();
- setTimeout(() => {
+ waitForAllCallsToFinish(service, 1, () => {
expect(service.fetch.calls.count()).toEqual(1);
expect(service.fetch).toHaveBeenCalledWith({ page: 1 });
expect(Polling.stop).toHaveBeenCalled();
- done();
- }, 100);
- Vue.http.interceptors = _.without(Vue.http.interceptors, pollInterceptor);
+ Vue.http.interceptors = _.without(Vue.http.interceptors, pollInterceptor);
+
+ done();
+ });
});
});
@@ -169,10 +189,6 @@ describe('Poll', () => {
Vue.http.interceptors.push(pollInterceptor);
- const service = new ServiceMock('endpoint');
-
- spyOn(service, 'fetch').and.callThrough();
-
const Polling = new Poll({
resource: service,
method: 'fetch',
@@ -187,17 +203,22 @@ describe('Poll', () => {
});
spyOn(Polling, 'stop').and.callThrough();
+ spyOn(Polling, 'restart').and.callThrough();
Polling.makeRequest();
- setTimeout(() => {
+ waitForAllCallsToFinish(service, 2, () => {
+ Polling.stop();
+
expect(service.fetch.calls.count()).toEqual(2);
expect(service.fetch).toHaveBeenCalledWith({ page: 1 });
expect(Polling.stop).toHaveBeenCalled();
- done();
- }, 10);
+ expect(Polling.restart).toHaveBeenCalled();
- Vue.http.interceptors = _.without(Vue.http.interceptors, pollInterceptor);
+ Vue.http.interceptors = _.without(Vue.http.interceptors, pollInterceptor);
+
+ done();
+ });
});
});
});
diff --git a/spec/javascripts/lib/utils/text_utility_spec.js b/spec/javascripts/lib/utils/text_utility_spec.js
index 4200e943121..daef9b93fa5 100644
--- a/spec/javascripts/lib/utils/text_utility_spec.js
+++ b/spec/javascripts/lib/utils/text_utility_spec.js
@@ -1,110 +1,108 @@
require('~/lib/utils/text_utility');
-(() => {
- describe('text_utility', () => {
- describe('gl.text.getTextWidth', () => {
- it('returns zero width when no text is passed', () => {
- expect(gl.text.getTextWidth('')).toBe(0);
- });
+describe('text_utility', () => {
+ describe('gl.text.getTextWidth', () => {
+ it('returns zero width when no text is passed', () => {
+ expect(gl.text.getTextWidth('')).toBe(0);
+ });
- it('returns zero width when no text is passed and font is passed', () => {
- expect(gl.text.getTextWidth('', '100px sans-serif')).toBe(0);
- });
+ it('returns zero width when no text is passed and font is passed', () => {
+ expect(gl.text.getTextWidth('', '100px sans-serif')).toBe(0);
+ });
- it('returns width when text is passed', () => {
- expect(gl.text.getTextWidth('foo') > 0).toBe(true);
- });
+ it('returns width when text is passed', () => {
+ expect(gl.text.getTextWidth('foo') > 0).toBe(true);
+ });
- it('returns bigger width when font is larger', () => {
- const largeFont = gl.text.getTextWidth('foo', '100px sans-serif');
- const regular = gl.text.getTextWidth('foo', '10px sans-serif');
- expect(largeFont > regular).toBe(true);
- });
+ it('returns bigger width when font is larger', () => {
+ const largeFont = gl.text.getTextWidth('foo', '100px sans-serif');
+ const regular = gl.text.getTextWidth('foo', '10px sans-serif');
+ expect(largeFont > regular).toBe(true);
});
+ });
- describe('gl.text.pluralize', () => {
- it('returns pluralized', () => {
- expect(gl.text.pluralize('test', 2)).toBe('tests');
- });
+ describe('gl.text.pluralize', () => {
+ it('returns pluralized', () => {
+ expect(gl.text.pluralize('test', 2)).toBe('tests');
+ });
- it('returns pluralized when count is 0', () => {
- expect(gl.text.pluralize('test', 0)).toBe('tests');
- });
+ it('returns pluralized when count is 0', () => {
+ expect(gl.text.pluralize('test', 0)).toBe('tests');
+ });
- it('does not return pluralized', () => {
- expect(gl.text.pluralize('test', 1)).toBe('test');
- });
+ it('does not return pluralized', () => {
+ 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+');
- });
+ 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);
- });
+ it('returns exact number for count < 100', () => {
+ expect(gl.text.highCountTrim(45)).toBe(45);
});
+ });
- describe('gl.text.insertText', () => {
- let textArea;
+ describe('gl.text.insertText', () => {
+ let textArea;
- beforeAll(() => {
- textArea = document.createElement('textarea');
- document.querySelector('body').appendChild(textArea);
- });
+ beforeAll(() => {
+ textArea = document.createElement('textarea');
+ document.querySelector('body').appendChild(textArea);
+ });
- afterAll(() => {
- textArea.parentNode.removeChild(textArea);
- });
+ afterAll(() => {
+ textArea.parentNode.removeChild(textArea);
+ });
- describe('without selection', () => {
- it('inserts the tag on an empty line', () => {
- const initialValue = '';
+ describe('without selection', () => {
+ it('inserts the tag on an empty line', () => {
+ const initialValue = '';
- textArea.value = initialValue;
- textArea.selectionStart = 0;
- textArea.selectionEnd = 0;
+ textArea.value = initialValue;
+ textArea.selectionStart = 0;
+ textArea.selectionEnd = 0;
- gl.text.insertText(textArea, textArea.value, '*', null, '', false);
+ gl.text.insertText(textArea, textArea.value, '*', null, '', false);
- expect(textArea.value).toEqual(`${initialValue}* `);
- });
+ expect(textArea.value).toEqual(`${initialValue}* `);
+ });
- it('inserts the tag on a new line if the current one is not empty', () => {
- const initialValue = 'some text';
+ it('inserts the tag on a new line if the current one is not empty', () => {
+ const initialValue = 'some text';
- textArea.value = initialValue;
- textArea.setSelectionRange(initialValue.length, initialValue.length);
+ textArea.value = initialValue;
+ textArea.setSelectionRange(initialValue.length, initialValue.length);
- gl.text.insertText(textArea, textArea.value, '*', null, '', false);
+ gl.text.insertText(textArea, textArea.value, '*', null, '', false);
- expect(textArea.value).toEqual(`${initialValue}\n* `);
- });
+ expect(textArea.value).toEqual(`${initialValue}\n* `);
+ });
- it('inserts the tag on the same line if the current line only contains spaces', () => {
- const initialValue = ' ';
+ it('inserts the tag on the same line if the current line only contains spaces', () => {
+ const initialValue = ' ';
- textArea.value = initialValue;
- textArea.setSelectionRange(initialValue.length, initialValue.length);
+ textArea.value = initialValue;
+ textArea.setSelectionRange(initialValue.length, initialValue.length);
- gl.text.insertText(textArea, textArea.value, '*', null, '', false);
+ gl.text.insertText(textArea, textArea.value, '*', null, '', false);
- expect(textArea.value).toEqual(`${initialValue}* `);
- });
+ expect(textArea.value).toEqual(`${initialValue}* `);
+ });
- it('inserts the tag on the same line if the current line only contains tabs', () => {
- const initialValue = '\t\t\t';
+ it('inserts the tag on the same line if the current line only contains tabs', () => {
+ const initialValue = '\t\t\t';
- textArea.value = initialValue;
- textArea.setSelectionRange(initialValue.length, initialValue.length);
+ textArea.value = initialValue;
+ textArea.setSelectionRange(initialValue.length, initialValue.length);
- gl.text.insertText(textArea, textArea.value, '*', null, '', false);
+ gl.text.insertText(textArea, textArea.value, '*', null, '', false);
- expect(textArea.value).toEqual(`${initialValue}* `);
- });
+ expect(textArea.value).toEqual(`${initialValue}* `);
});
});
});
-})();
+});
diff --git a/spec/javascripts/merged_buttons_spec.js b/spec/javascripts/merged_buttons_spec.js
new file mode 100644
index 00000000000..b5c5e60dd97
--- /dev/null
+++ b/spec/javascripts/merged_buttons_spec.js
@@ -0,0 +1,44 @@
+/* global MergedButtons */
+
+import '~/merged_buttons';
+
+describe('MergedButtons', () => {
+ const fixturesPath = 'merge_requests/merged_merge_request.html.raw';
+ preloadFixtures(fixturesPath);
+
+ beforeEach(() => {
+ loadFixtures(fixturesPath);
+ this.mergedButtons = new MergedButtons();
+ this.$removeBranchWidget = $('.remove_source_branch_widget:not(.failed)');
+ this.$removeBranchProgress = $('.remove_source_branch_in_progress');
+ this.$removeBranchFailed = $('.remove_source_branch_widget.failed');
+ this.$removeBranchButton = $('.remove_source_branch');
+ });
+
+ describe('removeSourceBranch', () => {
+ it('shows loader', () => {
+ $('.remove_source_branch').trigger('click');
+ expect(this.$removeBranchProgress).toBeVisible();
+ expect(this.$removeBranchWidget).not.toBeVisible();
+ });
+ });
+
+ describe('removeBranchSuccess', () => {
+ it('refreshes page when branch removed', () => {
+ spyOn(gl.utils, 'refreshCurrentPage').and.stub();
+ const response = { status: 200 };
+ this.$removeBranchButton.trigger('ajax:success', response, 'xhr');
+ expect(gl.utils.refreshCurrentPage).toHaveBeenCalled();
+ });
+ });
+
+ describe('removeBranchError', () => {
+ it('shows error message', () => {
+ const response = { status: 500 };
+ this.$removeBranchButton.trigger('ajax:error', response, 'xhr');
+ expect(this.$removeBranchFailed).toBeVisible();
+ expect(this.$removeBranchProgress).not.toBeVisible();
+ expect(this.$removeBranchWidget).not.toBeVisible();
+ });
+ });
+});
diff --git a/spec/javascripts/user_callout_spec.js b/spec/javascripts/user_callout_spec.js
index c0375ebc61c..28d0c7dcd99 100644
--- a/spec/javascripts/user_callout_spec.js
+++ b/spec/javascripts/user_callout_spec.js
@@ -14,7 +14,6 @@ describe('UserCallout', function () {
this.userCallout = new UserCallout();
this.closeButton = $('.js-close-callout.close');
this.userCalloutBtn = $('.js-close-callout:not(.close)');
- this.userCalloutContainer = $('.user-callout');
});
it('hides when user clicks on the dismiss-icon', (done) => {
diff --git a/spec/javascripts/vue_pipelines_index/async_button_spec.js b/spec/javascripts/vue_pipelines_index/async_button_spec.js
index bc8e504c413..6e910d2dc71 100644
--- a/spec/javascripts/vue_pipelines_index/async_button_spec.js
+++ b/spec/javascripts/vue_pipelines_index/async_button_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import asyncButtonComp from '~/vue_pipelines_index/components/async_button';
+import asyncButtonComp from '~/vue_pipelines_index/components/async_button.vue';
describe('Pipelines Async Button', () => {
let component;
diff --git a/spec/javascripts/vue_pipelines_index/empty_state_spec.js b/spec/javascripts/vue_pipelines_index/empty_state_spec.js
index 733337168dc..2b10d54babe 100644
--- a/spec/javascripts/vue_pipelines_index/empty_state_spec.js
+++ b/spec/javascripts/vue_pipelines_index/empty_state_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import emptyStateComp from '~/vue_pipelines_index/components/empty_state';
+import emptyStateComp from '~/vue_pipelines_index/components/empty_state.vue';
describe('Pipelines Empty State', () => {
let component;
diff --git a/spec/javascripts/vue_pipelines_index/error_state_spec.js b/spec/javascripts/vue_pipelines_index/error_state_spec.js
index 524e018b1fa..7999c15c18d 100644
--- a/spec/javascripts/vue_pipelines_index/error_state_spec.js
+++ b/spec/javascripts/vue_pipelines_index/error_state_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import errorStateComp from '~/vue_pipelines_index/components/error_state';
+import errorStateComp from '~/vue_pipelines_index/components/error_state.vue';
describe('Pipelines Error State', () => {
let component;
diff --git a/spec/lib/banzai/filter/issuable_state_filter_spec.rb b/spec/lib/banzai/filter/issuable_state_filter_spec.rb
index 603b79a323c..5cb98163746 100644
--- a/spec/lib/banzai/filter/issuable_state_filter_spec.rb
+++ b/spec/lib/banzai/filter/issuable_state_filter_spec.rb
@@ -6,8 +6,8 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
let(:user) { create(:user) }
- def create_link(data)
- link_to('text', '', class: 'gfm has-tooltip', data: data)
+ def create_link(text, data)
+ link_to(text, '', class: 'gfm has-tooltip', data: data)
end
it 'ignores non-GFM links' do
@@ -19,16 +19,37 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
it 'ignores non-issuable links' do
project = create(:empty_project, :public)
- link = create_link(project: project, reference_type: 'issue')
+ link = create_link('text', project: project, reference_type: 'issue')
doc = filter(link, current_user: user)
expect(doc.css('a').last.text).to eq('text')
end
+ it 'ignores issuable links with empty content' do
+ issue = create(:issue, :closed)
+ link = create_link('', issue: issue.id, reference_type: 'issue')
+ doc = filter(link, current_user: user)
+
+ expect(doc.css('a').last.text).to eq('')
+ end
+
+ it 'adds text with standard formatting' do
+ issue = create(:issue, :closed)
+ link = create_link(
+ 'something <strong>else</strong>'.html_safe,
+ issue: issue.id,
+ reference_type: 'issue'
+ )
+ doc = filter(link, current_user: user)
+
+ expect(doc.css('a').last.inner_html).
+ to eq('something <strong>else</strong> [closed]')
+ end
+
context 'for issue references' do
it 'ignores open issue references' do
issue = create(:issue)
- link = create_link(issue: issue.id, reference_type: 'issue')
+ link = create_link('text', issue: issue.id, reference_type: 'issue')
doc = filter(link, current_user: user)
expect(doc.css('a').last.text).to eq('text')
@@ -36,7 +57,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
it 'ignores reopened issue references' do
reopened_issue = create(:issue, :reopened)
- link = create_link(issue: reopened_issue.id, reference_type: 'issue')
+ link = create_link('text', issue: reopened_issue.id, reference_type: 'issue')
doc = filter(link, current_user: user)
expect(doc.css('a').last.text).to eq('text')
@@ -44,7 +65,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
it 'appends [closed] to closed issue references' do
closed_issue = create(:issue, :closed)
- link = create_link(issue: closed_issue.id, reference_type: 'issue')
+ link = create_link('text', issue: closed_issue.id, reference_type: 'issue')
doc = filter(link, current_user: user)
expect(doc.css('a').last.text).to eq('text [closed]')
@@ -54,7 +75,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
context 'for merge request references' do
it 'ignores open merge request references' do
mr = create(:merge_request)
- link = create_link(merge_request: mr.id, reference_type: 'merge_request')
+ link = create_link('text', merge_request: mr.id, reference_type: 'merge_request')
doc = filter(link, current_user: user)
expect(doc.css('a').last.text).to eq('text')
@@ -62,7 +83,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
it 'ignores reopened merge request references' do
mr = create(:merge_request, :reopened)
- link = create_link(merge_request: mr.id, reference_type: 'merge_request')
+ link = create_link('text', merge_request: mr.id, reference_type: 'merge_request')
doc = filter(link, current_user: user)
expect(doc.css('a').last.text).to eq('text')
@@ -70,7 +91,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
it 'ignores locked merge request references' do
mr = create(:merge_request, :locked)
- link = create_link(merge_request: mr.id, reference_type: 'merge_request')
+ link = create_link('text', merge_request: mr.id, reference_type: 'merge_request')
doc = filter(link, current_user: user)
expect(doc.css('a').last.text).to eq('text')
@@ -78,7 +99,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
it 'appends [closed] to closed merge request references' do
mr = create(:merge_request, :closed)
- link = create_link(merge_request: mr.id, reference_type: 'merge_request')
+ link = create_link('text', merge_request: mr.id, reference_type: 'merge_request')
doc = filter(link, current_user: user)
expect(doc.css('a').last.text).to eq('text [closed]')
@@ -86,7 +107,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
it 'appends [merged] to merged merge request references' do
mr = create(:merge_request, :merged)
- link = create_link(merge_request: mr.id, reference_type: 'merge_request')
+ link = create_link('text', merge_request: mr.id, reference_type: 'merge_request')
doc = filter(link, current_user: user)
expect(doc.css('a').last.text).to eq('text [merged]')
diff --git a/spec/lib/banzai/reference_parser/base_parser_spec.rb b/spec/lib/banzai/reference_parser/base_parser_spec.rb
index a3141894c74..d5746107ee1 100644
--- a/spec/lib/banzai/reference_parser/base_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/base_parser_spec.rb
@@ -114,8 +114,27 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
expect(hash).to eq({ link => user })
end
- it 'returns an empty Hash when the list of nodes is empty' do
- expect(subject.grouped_objects_for_nodes([], User, 'data-user')).to eq({})
+ it 'returns an empty Hash when entry does not exist in the database' do
+ link = double(:link)
+
+ expect(link).to receive(:has_attribute?).
+ with('data-user').
+ and_return(true)
+
+ expect(link).to receive(:attr).
+ with('data-user').
+ and_return('1')
+
+ nodes = [link]
+ bad_id = user.id + 100
+
+ expect(subject).to receive(:unique_attribute_values).
+ with(nodes, 'data-user').
+ and_return([bad_id.to_s])
+
+ hash = subject.grouped_objects_for_nodes(nodes, User, 'data-user')
+
+ expect(hash).to eq({})
end
end
diff --git a/spec/lib/container_registry/path_spec.rb b/spec/lib/container_registry/path_spec.rb
index b9c4572c269..f3b3a9a715f 100644
--- a/spec/lib/container_registry/path_spec.rb
+++ b/spec/lib/container_registry/path_spec.rb
@@ -33,10 +33,20 @@ describe ContainerRegistry::Path do
end
describe '#to_s' do
- let(:path) { 'some/image' }
+ context 'when path does not have uppercase characters' do
+ let(:path) { 'some/image' }
- it 'return a string with a repository path' do
- expect(subject.to_s).to eq path
+ it 'return a string with a repository path' do
+ expect(subject.to_s).to eq 'some/image'
+ end
+ end
+
+ context 'when path has uppercase characters' do
+ let(:path) { 'SoMe/ImAgE' }
+
+ it 'return a string with a repository path' do
+ expect(subject.to_s).to eq 'some/image'
+ end
end
end
@@ -70,6 +80,12 @@ describe ContainerRegistry::Path do
it { is_expected.to be_valid }
end
+
+ context 'when path contains uppercase letters' do
+ let(:path) { 'Some/Registry' }
+
+ it { is_expected.to be_valid }
+ end
end
describe '#has_repository?' do
diff --git a/spec/lib/container_registry/tag_spec.rb b/spec/lib/container_registry/tag_spec.rb
index bc1912d8e6c..f8fffbdca41 100644
--- a/spec/lib/container_registry/tag_spec.rb
+++ b/spec/lib/container_registry/tag_spec.rb
@@ -50,6 +50,13 @@ describe ContainerRegistry::Tag do
end
end
+ describe '#location' do
+ it 'returns a full location of the tag' do
+ expect(tag.location)
+ .to eq 'registry.gitlab/group/test:tag'
+ end
+ end
+
context 'manifest processing' do
context 'schema v1' do
before do
diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb
index f1a1a71c528..40ac5a3ed37 100644
--- a/spec/lib/gitlab/ci/trace/stream_spec.rb
+++ b/spec/lib/gitlab/ci/trace/stream_spec.rb
@@ -17,12 +17,12 @@ describe Gitlab::Ci::Trace::Stream do
describe '#limit' do
let(:stream) do
described_class.new do
- StringIO.new("12345678")
+ StringIO.new((1..8).to_a.join("\n"))
end
end
- it 'if size is larger we start from beggining' do
- stream.limit(10)
+ it 'if size is larger we start from beginning' do
+ stream.limit(20)
expect(stream.tell).to eq(0)
end
@@ -30,17 +30,61 @@ describe Gitlab::Ci::Trace::Stream do
it 'if size is smaller we start from the end' do
stream.limit(2)
- expect(stream.tell).to eq(6)
+ expect(stream.raw).to eq("8")
+ end
+
+ context 'when the trace contains ANSI sequence and Unicode' do
+ let(:stream) do
+ described_class.new do
+ File.open(expand_fixture_path('trace/ansi-sequence-and-unicode'))
+ end
+ end
+
+ it 'forwards to the next linefeed, case 1' do
+ stream.limit(7)
+
+ result = stream.raw
+
+ expect(result).to eq('')
+ expect(result.encoding).to eq(Encoding.default_external)
+ end
+
+ it 'forwards to the next linefeed, case 2' do
+ stream.limit(29)
+
+ result = stream.raw
+
+ expect(result).to eq("\e[01;32m許功蓋\e[0m\n")
+ expect(result.encoding).to eq(Encoding.default_external)
+ end
+
+ # See https://gitlab.com/gitlab-org/gitlab-ce/issues/30796
+ it 'reads in binary, output as Encoding.default_external' do
+ stream.limit(52)
+
+ result = stream.html
+
+ expect(result).to eq("ヾ(´༎ຶД༎ຶ`)ノ<br><span class=\"term-fg-green\">許功蓋</span><br>")
+ expect(result.encoding).to eq(Encoding.default_external)
+ end
end
end
describe '#append' do
+ let(:tempfile) { Tempfile.new }
+
let(:stream) do
described_class.new do
- StringIO.new("12345678")
+ tempfile.write("12345678")
+ tempfile.rewind
+ tempfile
end
end
+ after do
+ tempfile.unlink
+ end
+
it "truncates and append content" do
stream.append("89", 4)
stream.seek(0)
@@ -48,6 +92,17 @@ describe Gitlab::Ci::Trace::Stream do
expect(stream.size).to eq(6)
expect(stream.raw).to eq("123489")
end
+
+ it 'appends in binary mode' do
+ '😺'.force_encoding('ASCII-8BIT').each_char.with_index do |byte, offset|
+ stream.append(byte, offset)
+ end
+
+ stream.seek(0)
+
+ expect(stream.size).to eq(4)
+ expect(stream.raw).to eq('😺')
+ end
end
describe '#set' do
@@ -167,7 +222,7 @@ describe Gitlab::Ci::Trace::Stream do
let(:data) { 'Coverage 1033 / 1051 LOC (98.29%) covered' }
let(:regex) { '\(\d+.\d+\%\) covered' }
- it { is_expected.to eq(98.29) }
+ it { is_expected.to eq("98.29") }
end
context 'valid content & bad regex' do
@@ -188,14 +243,14 @@ describe Gitlab::Ci::Trace::Stream do
let(:data) { ' (98.39%) covered. (98.29%) covered' }
let(:regex) { '\(\d+.\d+\%\) covered' }
- it { is_expected.to eq(98.29) }
+ it { is_expected.to eq("98.29") }
end
context 'using a regex capture' do
let(:data) { 'TOTAL 9926 3489 65%' }
let(:regex) { 'TOTAL\s+\d+\s+\d+\s+(\d{1,3}\%)' }
- it { is_expected.to eq(65) }
+ it { is_expected.to eq("65") }
end
end
end
diff --git a/spec/lib/gitlab/ci/trace_spec.rb b/spec/lib/gitlab/ci/trace_spec.rb
index 69e8dc9220d..9cb0b62590a 100644
--- a/spec/lib/gitlab/ci/trace_spec.rb
+++ b/spec/lib/gitlab/ci/trace_spec.rb
@@ -40,12 +40,24 @@ describe Gitlab::Ci::Trace do
describe '#extract_coverage' do
let(:regex) { '\(\d+.\d+\%\) covered' }
- before do
- trace.set('Coverage 1033 / 1051 LOC (98.29%) covered')
+ context 'matching coverage' do
+ before do
+ trace.set('Coverage 1033 / 1051 LOC (98.29%) covered')
+ end
+
+ it "returns valid coverage" do
+ expect(trace.extract_coverage(regex)).to eq("98.29")
+ end
end
- it "returns valid coverage" do
- expect(trace.extract_coverage(regex)).to eq(98.29)
+ context 'no coverage' do
+ before do
+ trace.set('No coverage')
+ end
+
+ it 'returs nil' do
+ expect(trace.extract_coverage(regex)).to be_nil
+ end
end
end
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 4ac79454647..a044b871730 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -175,6 +175,50 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
end
end
+ describe '#true_value' do
+ context 'using PostgreSQL' do
+ before do
+ expect(Gitlab::Database).to receive(:postgresql?).and_return(true)
+ end
+
+ it 'returns the appropriate value' do
+ expect(model.true_value).to eq("'t'")
+ end
+ end
+
+ context 'using MySQL' do
+ before do
+ expect(Gitlab::Database).to receive(:postgresql?).and_return(false)
+ end
+
+ it 'returns the appropriate value' do
+ expect(model.true_value).to eq(1)
+ end
+ end
+ end
+
+ describe '#false_value' do
+ context 'using PostgreSQL' do
+ before do
+ expect(Gitlab::Database).to receive(:postgresql?).and_return(true)
+ end
+
+ it 'returns the appropriate value' do
+ expect(model.false_value).to eq("'f'")
+ end
+ end
+
+ context 'using MySQL' do
+ before do
+ expect(Gitlab::Database).to receive(:postgresql?).and_return(false)
+ end
+
+ it 'returns the appropriate value' do
+ expect(model.false_value).to eq(0)
+ end
+ end
+ end
+
describe '#update_column_in_batches' do
before do
create_list(:empty_project, 5)
@@ -294,4 +338,392 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
end
end
end
+
+ describe '#rename_column_concurrently' do
+ context 'in a transaction' do
+ it 'raises RuntimeError' do
+ allow(model).to receive(:transaction_open?).and_return(true)
+
+ expect { model.rename_column_concurrently(:users, :old, :new) }.
+ to raise_error(RuntimeError)
+ end
+ end
+
+ context 'outside a transaction' do
+ let(:old_column) do
+ double(:column,
+ type: :integer,
+ limit: 8,
+ default: 0,
+ null: false,
+ precision: 5,
+ scale: 1)
+ end
+
+ let(:trigger_name) { model.rename_trigger_name(:users, :old, :new) }
+
+ before do
+ allow(model).to receive(:transaction_open?).and_return(false)
+ allow(model).to receive(:column_for).and_return(old_column)
+
+ # Since MySQL and PostgreSQL use different quoting styles we'll just
+ # stub the methods used for this to make testing easier.
+ allow(model).to receive(:quote_column_name) { |name| name.to_s }
+ allow(model).to receive(:quote_table_name) { |name| name.to_s }
+ end
+
+ context 'using MySQL' do
+ it 'renames a column concurrently' do
+ allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
+
+ expect(model).to receive(:install_rename_triggers_for_mysql).
+ with(trigger_name, 'users', 'old', 'new')
+
+ expect(model).to receive(:add_column).
+ with(:users, :new, :integer,
+ limit: old_column.limit,
+ default: old_column.default,
+ null: old_column.null,
+ precision: old_column.precision,
+ scale: old_column.scale)
+
+ expect(model).to receive(:update_column_in_batches)
+
+ expect(model).to receive(:copy_indexes).with(:users, :old, :new)
+ expect(model).to receive(:copy_foreign_keys).with(:users, :old, :new)
+
+ model.rename_column_concurrently(:users, :old, :new)
+ end
+ end
+
+ context 'using PostgreSQL' do
+ it 'renames a column concurrently' do
+ allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
+
+ expect(model).to receive(:install_rename_triggers_for_postgresql).
+ with(trigger_name, 'users', 'old', 'new')
+
+ expect(model).to receive(:add_column).
+ with(:users, :new, :integer,
+ limit: old_column.limit,
+ default: old_column.default,
+ null: old_column.null,
+ precision: old_column.precision,
+ scale: old_column.scale)
+
+ expect(model).to receive(:update_column_in_batches)
+
+ expect(model).to receive(:copy_indexes).with(:users, :old, :new)
+ expect(model).to receive(:copy_foreign_keys).with(:users, :old, :new)
+
+ model.rename_column_concurrently(:users, :old, :new)
+ end
+ end
+ end
+ end
+
+ describe '#cleanup_concurrent_column_rename' do
+ it 'cleans up the renaming procedure for PostgreSQL' do
+ allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
+
+ expect(model).to receive(:remove_rename_triggers_for_postgresql).
+ with(:users, /trigger_.{12}/)
+
+ expect(model).to receive(:remove_column).with(:users, :old)
+
+ model.cleanup_concurrent_column_rename(:users, :old, :new)
+ end
+
+ it 'cleans up the renaming procedure for MySQL' do
+ allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
+
+ expect(model).to receive(:remove_rename_triggers_for_mysql).
+ with(/trigger_.{12}/)
+
+ expect(model).to receive(:remove_column).with(:users, :old)
+
+ model.cleanup_concurrent_column_rename(:users, :old, :new)
+ end
+ end
+
+ describe '#change_column_type_concurrently' do
+ it 'changes the column type' do
+ expect(model).to receive(:rename_column_concurrently).
+ with('users', 'username', 'username_for_type_change', type: :text)
+
+ model.change_column_type_concurrently('users', 'username', :text)
+ end
+ end
+
+ describe '#cleanup_concurrent_column_type_change' do
+ it 'cleans up the type changing procedure' do
+ expect(model).to receive(:cleanup_concurrent_column_rename).
+ with('users', 'username', 'username_for_type_change')
+
+ expect(model).to receive(:rename_column).
+ with('users', 'username_for_type_change', 'username')
+
+ model.cleanup_concurrent_column_type_change('users', 'username')
+ end
+ end
+
+ describe '#install_rename_triggers_for_postgresql' do
+ it 'installs the triggers for PostgreSQL' do
+ expect(model).to receive(:execute).
+ with(/CREATE OR REPLACE FUNCTION foo()/m)
+
+ expect(model).to receive(:execute).
+ with(/CREATE TRIGGER foo/m)
+
+ model.install_rename_triggers_for_postgresql('foo', :users, :old, :new)
+ end
+ end
+
+ describe '#install_rename_triggers_for_mysql' do
+ it 'installs the triggers for MySQL' do
+ expect(model).to receive(:execute).
+ with(/CREATE TRIGGER foo_insert.+ON users/m)
+
+ expect(model).to receive(:execute).
+ with(/CREATE TRIGGER foo_update.+ON users/m)
+
+ model.install_rename_triggers_for_mysql('foo', :users, :old, :new)
+ end
+ end
+
+ describe '#remove_rename_triggers_for_postgresql' do
+ it 'removes the function and trigger' do
+ expect(model).to receive(:execute).with('DROP TRIGGER foo ON bar')
+ expect(model).to receive(:execute).with('DROP FUNCTION foo()')
+
+ model.remove_rename_triggers_for_postgresql('bar', 'foo')
+ end
+ end
+
+ describe '#remove_rename_triggers_for_mysql' do
+ it 'removes the triggers' do
+ expect(model).to receive(:execute).with('DROP TRIGGER foo_insert')
+ expect(model).to receive(:execute).with('DROP TRIGGER foo_update')
+
+ model.remove_rename_triggers_for_mysql('foo')
+ end
+ end
+
+ describe '#rename_trigger_name' do
+ it 'returns a String' do
+ expect(model.rename_trigger_name(:users, :foo, :bar)).
+ to match(/trigger_.{12}/)
+ end
+ end
+
+ describe '#indexes_for' do
+ it 'returns the indexes for a column' do
+ idx1 = double(:idx, columns: %w(project_id))
+ idx2 = double(:idx, columns: %w(user_id))
+
+ allow(model).to receive(:indexes).with('table').and_return([idx1, idx2])
+
+ expect(model.indexes_for('table', :user_id)).to eq([idx2])
+ end
+ end
+
+ describe '#foreign_keys_for' do
+ it 'returns the foreign keys for a column' do
+ fk1 = double(:fk, column: 'project_id')
+ fk2 = double(:fk, column: 'user_id')
+
+ allow(model).to receive(:foreign_keys).with('table').and_return([fk1, fk2])
+
+ expect(model.foreign_keys_for('table', :user_id)).to eq([fk2])
+ end
+ end
+
+ describe '#copy_indexes' do
+ context 'using a regular index using a single column' do
+ it 'copies the index' do
+ index = double(:index,
+ columns: %w(project_id),
+ name: 'index_on_issues_project_id',
+ using: nil,
+ where: nil,
+ opclasses: {},
+ unique: false,
+ lengths: [],
+ orders: [])
+
+ allow(model).to receive(:indexes_for).with(:issues, 'project_id').
+ and_return([index])
+
+ expect(model).to receive(:add_concurrent_index).
+ with(:issues,
+ %w(gl_project_id),
+ unique: false,
+ name: 'index_on_issues_gl_project_id',
+ length: [],
+ order: [])
+
+ model.copy_indexes(:issues, :project_id, :gl_project_id)
+ end
+ end
+
+ context 'using a regular index with multiple columns' do
+ it 'copies the index' do
+ index = double(:index,
+ columns: %w(project_id foobar),
+ name: 'index_on_issues_project_id_foobar',
+ using: nil,
+ where: nil,
+ opclasses: {},
+ unique: false,
+ lengths: [],
+ orders: [])
+
+ allow(model).to receive(:indexes_for).with(:issues, 'project_id').
+ and_return([index])
+
+ expect(model).to receive(:add_concurrent_index).
+ with(:issues,
+ %w(gl_project_id foobar),
+ unique: false,
+ name: 'index_on_issues_gl_project_id_foobar',
+ length: [],
+ order: [])
+
+ model.copy_indexes(:issues, :project_id, :gl_project_id)
+ end
+ end
+
+ context 'using an index with a WHERE clause' do
+ it 'copies the index' do
+ index = double(:index,
+ columns: %w(project_id),
+ name: 'index_on_issues_project_id',
+ using: nil,
+ where: 'foo',
+ opclasses: {},
+ unique: false,
+ lengths: [],
+ orders: [])
+
+ allow(model).to receive(:indexes_for).with(:issues, 'project_id').
+ and_return([index])
+
+ expect(model).to receive(:add_concurrent_index).
+ with(:issues,
+ %w(gl_project_id),
+ unique: false,
+ name: 'index_on_issues_gl_project_id',
+ length: [],
+ order: [],
+ where: 'foo')
+
+ model.copy_indexes(:issues, :project_id, :gl_project_id)
+ end
+ end
+
+ context 'using an index with a USING clause' do
+ it 'copies the index' do
+ index = double(:index,
+ columns: %w(project_id),
+ name: 'index_on_issues_project_id',
+ where: nil,
+ using: 'foo',
+ opclasses: {},
+ unique: false,
+ lengths: [],
+ orders: [])
+
+ allow(model).to receive(:indexes_for).with(:issues, 'project_id').
+ and_return([index])
+
+ expect(model).to receive(:add_concurrent_index).
+ with(:issues,
+ %w(gl_project_id),
+ unique: false,
+ name: 'index_on_issues_gl_project_id',
+ length: [],
+ order: [],
+ using: 'foo')
+
+ model.copy_indexes(:issues, :project_id, :gl_project_id)
+ end
+ end
+
+ context 'using an index with custom operator classes' do
+ it 'copies the index' do
+ index = double(:index,
+ columns: %w(project_id),
+ name: 'index_on_issues_project_id',
+ using: nil,
+ where: nil,
+ opclasses: { 'project_id' => 'bar' },
+ unique: false,
+ lengths: [],
+ orders: [])
+
+ allow(model).to receive(:indexes_for).with(:issues, 'project_id').
+ and_return([index])
+
+ expect(model).to receive(:add_concurrent_index).
+ with(:issues,
+ %w(gl_project_id),
+ unique: false,
+ name: 'index_on_issues_gl_project_id',
+ length: [],
+ order: [],
+ opclasses: { 'gl_project_id' => 'bar' })
+
+ model.copy_indexes(:issues, :project_id, :gl_project_id)
+ end
+ end
+
+ describe 'using an index of which the name does not contain the source column' do
+ it 'raises RuntimeError' do
+ index = double(:index,
+ columns: %w(project_id),
+ name: 'index_foobar_index',
+ using: nil,
+ where: nil,
+ opclasses: {},
+ unique: false,
+ lengths: [],
+ orders: [])
+
+ allow(model).to receive(:indexes_for).with(:issues, 'project_id').
+ and_return([index])
+
+ expect { model.copy_indexes(:issues, :project_id, :gl_project_id) }.
+ to raise_error(RuntimeError)
+ end
+ end
+ end
+
+ describe '#copy_foreign_keys' do
+ it 'copies foreign keys from one column to another' do
+ fk = double(:fk,
+ from_table: 'issues',
+ to_table: 'projects',
+ on_delete: :cascade)
+
+ allow(model).to receive(:foreign_keys_for).with(:issues, :project_id).
+ and_return([fk])
+
+ expect(model).to receive(:add_concurrent_foreign_key).
+ with('issues', 'projects', column: :gl_project_id, on_delete: :cascade)
+
+ model.copy_foreign_keys(:issues, :project_id, :gl_project_id)
+ end
+ end
+
+ describe '#column_for' do
+ it 'returns a column object for an existing column' do
+ column = model.column_for(:users, :id)
+
+ expect(column.name).to eq('id')
+ end
+
+ it 'returns nil when a column does not exist' do
+ expect(model.column_for(:users, :kittens)).to be_nil
+ end
+ end
end
diff --git a/spec/lib/gitlab/database/multi_threaded_migration_spec.rb b/spec/lib/gitlab/database/multi_threaded_migration_spec.rb
new file mode 100644
index 00000000000..6c45f13bb5a
--- /dev/null
+++ b/spec/lib/gitlab/database/multi_threaded_migration_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+describe Gitlab::Database::MultiThreadedMigration do
+ let(:migration) do
+ Class.new { include Gitlab::Database::MultiThreadedMigration }.new
+ end
+
+ describe '#connection' do
+ after do
+ Thread.current[described_class::MULTI_THREAD_AR_CONNECTION] = nil
+ end
+
+ it 'returns the thread-local connection if present' do
+ Thread.current[described_class::MULTI_THREAD_AR_CONNECTION] = 10
+
+ expect(migration.connection).to eq(10)
+ end
+
+ it 'returns the global connection if no thread-local connection was set' do
+ expect(migration.connection).to eq(ActiveRecord::Base.connection)
+ end
+ end
+
+ describe '#with_multiple_threads' do
+ it 'starts multiple threads and yields the supplied block in every thread' do
+ output = Queue.new
+
+ migration.with_multiple_threads(2) do
+ output << migration.connection.execute('SELECT 1')
+ end
+
+ expect(output.size).to eq(2)
+ end
+
+ it 'joins the threads when the join parameter is set' do
+ expect_any_instance_of(Thread).to receive(:join).and_call_original
+
+ migration.with_multiple_threads(1) { }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
index 4ce4e6e1034..9b1d66a1b1c 100644
--- a/spec/lib/gitlab/database_spec.rb
+++ b/spec/lib/gitlab/database_spec.rb
@@ -150,13 +150,13 @@ describe Gitlab::Database, lib: true do
it 'returns correct value for PostgreSQL' do
expect(described_class).to receive(:postgresql?).and_return(true)
- expect(MigrationTest.new.true_value).to eq "'t'"
+ expect(described_class.true_value).to eq "'t'"
end
it 'returns correct value for MySQL' do
expect(described_class).to receive(:postgresql?).and_return(false)
- expect(MigrationTest.new.true_value).to eq 1
+ expect(described_class.true_value).to eq 1
end
end
@@ -164,13 +164,13 @@ describe Gitlab::Database, lib: true do
it 'returns correct value for PostgreSQL' do
expect(described_class).to receive(:postgresql?).and_return(true)
- expect(MigrationTest.new.false_value).to eq "'f'"
+ expect(described_class.false_value).to eq "'f'"
end
it 'returns correct value for MySQL' do
expect(described_class).to receive(:postgresql?).and_return(false)
- expect(MigrationTest.new.false_value).to eq 0
+ expect(described_class.false_value).to eq 0
end
end
end
diff --git a/spec/lib/gitlab/etag_caching/router_spec.rb b/spec/lib/gitlab/etag_caching/router_spec.rb
new file mode 100644
index 00000000000..f3dacb4ef04
--- /dev/null
+++ b/spec/lib/gitlab/etag_caching/router_spec.rb
@@ -0,0 +1,83 @@
+require 'spec_helper'
+
+describe Gitlab::EtagCaching::Router do
+ it 'matches issue notes endpoint' do
+ env = build_env(
+ '/my-group/and-subgroup/here-comes-the-project/noteable/issue/1/notes'
+ )
+
+ result = described_class.match(env)
+
+ expect(result).to be_present
+ expect(result.name).to eq 'issue_notes'
+ end
+
+ it 'matches issue title endpoint' do
+ env = build_env(
+ '/my-group/my-project/issues/123/rendered_title'
+ )
+
+ result = described_class.match(env)
+
+ expect(result).to be_present
+ expect(result.name).to eq 'issue_title'
+ end
+
+ it 'matches project pipelines endpoint' do
+ env = build_env(
+ '/my-group/my-project/pipelines.json'
+ )
+
+ result = described_class.match(env)
+
+ expect(result).to be_present
+ expect(result.name).to eq 'project_pipelines'
+ end
+
+ it 'matches commit pipelines endpoint' do
+ env = build_env(
+ '/my-group/my-project/commit/aa8260d253a53f73f6c26c734c72fdd600f6e6d4/pipelines.json'
+ )
+
+ result = described_class.match(env)
+
+ expect(result).to be_present
+ expect(result.name).to eq 'commit_pipelines'
+ end
+
+ it 'matches new merge request pipelines endpoint' do
+ env = build_env(
+ '/my-group/my-project/merge_requests/new.json'
+ )
+
+ result = described_class.match(env)
+
+ expect(result).to be_present
+ expect(result.name).to eq 'new_merge_request_pipelines'
+ end
+
+ it 'matches merge request pipelines endpoint' do
+ env = build_env(
+ '/my-group/my-project/merge_requests/234/pipelines.json'
+ )
+
+ result = described_class.match(env)
+
+ expect(result).to be_present
+ expect(result.name).to eq 'merge_request_pipelines'
+ end
+
+ it 'does not match blob with confusing name' do
+ env = build_env(
+ '/my-group/my-project/blob/master/pipelines.json'
+ )
+
+ result = described_class.match(env)
+
+ expect(result).to be_blank
+ end
+
+ def build_env(path)
+ { 'PATH_INFO' => path }
+ end
+end
diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb
index 3f494257545..e6a07a58d73 100644
--- a/spec/lib/gitlab/git/blob_spec.rb
+++ b/spec/lib/gitlab/git/blob_spec.rb
@@ -234,7 +234,7 @@ describe Gitlab::Git::Blob, seed_helper: true do
it { expect(blob.lfs_pointer?).to eq(true) }
it { expect(blob.lfs_oid).to eq("4206f951d2691c78aac4c0ce9f2b23580b2c92cdcc4336e1028742c0274938e0") }
- it { expect(blob.lfs_size).to eq("19548") }
+ it { expect(blob.lfs_size).to eq(19548) }
it { expect(blob.id).to eq("f4d76af13003d1106be7ac8c5a2a3d37ddf32c2a") }
it { expect(blob.name).to eq("image.jpg") }
it { expect(blob.path).to eq("files/lfs/image.jpg") }
@@ -273,7 +273,7 @@ describe Gitlab::Git::Blob, seed_helper: true do
it { expect(blob.lfs_pointer?).to eq(false) }
it { expect(blob.lfs_oid).to eq(nil) }
- it { expect(blob.lfs_size).to eq("1575078") }
+ it { expect(blob.lfs_size).to eq(1575078) }
it { expect(blob.id).to eq("5ae35296e1f95c1ef9feda1241477ed29a448572") }
it { expect(blob.name).to eq("picture-invalid.png") }
it { expect(blob.path).to eq("files/lfs/picture-invalid.png") }
diff --git a/spec/lib/gitlab/git/env_spec.rb b/spec/lib/gitlab/git/env_spec.rb
new file mode 100644
index 00000000000..d9df99bfe05
--- /dev/null
+++ b/spec/lib/gitlab/git/env_spec.rb
@@ -0,0 +1,102 @@
+require 'spec_helper'
+
+describe Gitlab::Git::Env do
+ describe "#set" do
+ context 'with RequestStore.store disabled' do
+ before do
+ allow(RequestStore).to receive(:active?).and_return(false)
+ end
+
+ it 'does not store anything' do
+ described_class.set(GIT_OBJECT_DIRECTORY: 'foo')
+
+ expect(described_class.all).to be_empty
+ end
+ end
+
+ context 'with RequestStore.store enabled' do
+ before do
+ allow(RequestStore).to receive(:active?).and_return(true)
+ end
+
+ it 'whitelist some `GIT_*` variables and stores them using RequestStore' do
+ described_class.set(
+ GIT_OBJECT_DIRECTORY: 'foo',
+ GIT_ALTERNATE_OBJECT_DIRECTORIES: 'bar',
+ GIT_EXEC_PATH: 'baz',
+ PATH: '~/.bin:/bin')
+
+ expect(described_class[:GIT_OBJECT_DIRECTORY]).to eq('foo')
+ expect(described_class[:GIT_ALTERNATE_OBJECT_DIRECTORIES]).to eq('bar')
+ expect(described_class[:GIT_EXEC_PATH]).to be_nil
+ expect(described_class[:bar]).to be_nil
+ end
+ end
+ end
+
+ describe "#all" do
+ context 'with RequestStore.store enabled' do
+ before do
+ allow(RequestStore).to receive(:active?).and_return(true)
+ described_class.set(
+ GIT_OBJECT_DIRECTORY: 'foo',
+ GIT_ALTERNATE_OBJECT_DIRECTORIES: 'bar')
+ end
+
+ it 'returns an env hash' do
+ expect(described_class.all).to eq({
+ 'GIT_OBJECT_DIRECTORY' => 'foo',
+ 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => 'bar'
+ })
+ end
+ end
+ end
+
+ describe "#[]" do
+ context 'with RequestStore.store enabled' do
+ before do
+ allow(RequestStore).to receive(:active?).and_return(true)
+ end
+
+ before do
+ described_class.set(
+ GIT_OBJECT_DIRECTORY: 'foo',
+ GIT_ALTERNATE_OBJECT_DIRECTORIES: 'bar')
+ end
+
+ it 'returns a stored value for an existing key' do
+ expect(described_class[:GIT_OBJECT_DIRECTORY]).to eq('foo')
+ end
+
+ it 'returns nil for an non-existing key' do
+ expect(described_class[:foo]).to be_nil
+ end
+ end
+ end
+
+ describe 'thread-safety' do
+ context 'with RequestStore.store enabled' do
+ before do
+ allow(RequestStore).to receive(:active?).and_return(true)
+ described_class.set(GIT_OBJECT_DIRECTORY: 'foo')
+ end
+
+ it 'is thread-safe' do
+ another_thread = Thread.new do
+ described_class.set(GIT_OBJECT_DIRECTORY: 'bar')
+
+ Thread.stop
+ described_class[:GIT_OBJECT_DIRECTORY]
+ end
+
+ # Ensure another_thread runs first
+ sleep 0.1 until another_thread.stop?
+
+ expect(described_class[:GIT_OBJECT_DIRECTORY]).to eq('foo')
+
+ another_thread.run
+ expect(another_thread.value).to eq('bar')
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 7e8bb796e03..3d6d7292b42 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -24,18 +24,49 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
- context 'with gitaly enabled' do
- before { stub_gitaly }
+ # TODO: Uncomment when feature is reenabled
+ # context 'with gitaly enabled' do
+ # before { stub_gitaly }
+ #
+ # it 'gets the branch name from GitalyClient' do
+ # expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name)
+ # repository.root_ref
+ # end
+ #
+ # it 'wraps GRPC exceptions' do
+ # expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name).
+ # and_raise(GRPC::Unknown)
+ # expect { repository.root_ref }.to raise_error(Gitlab::Git::CommandError)
+ # end
+ # end
+ end
- it 'gets the branch name from GitalyClient' do
- expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name)
- repository.root_ref
+ describe "#rugged" do
+ context 'with no Git env stored' do
+ before do
+ expect(Gitlab::Git::Env).to receive(:all).and_return({})
+ end
+
+ it "whitelist some variables and pass them via the alternates keyword argument" do
+ expect(Rugged::Repository).to receive(:new).with(repository.path, alternates: [])
+
+ repository.rugged
end
+ end
- it 'wraps GRPC exceptions' do
- expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name).
- and_raise(GRPC::Unknown)
- expect { repository.root_ref }.to raise_error(Gitlab::Git::CommandError)
+ context 'with some Git env stored' do
+ before do
+ expect(Gitlab::Git::Env).to receive(:all).and_return({
+ 'GIT_OBJECT_DIRECTORY' => 'foo',
+ 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => 'bar',
+ 'GIT_OTHER' => 'another_env'
+ })
+ end
+
+ it "whitelist some variables and pass them via the alternates keyword argument" do
+ expect(Rugged::Repository).to receive(:new).with(repository.path, alternates: %w[foo bar])
+
+ repository.rugged
end
end
end
@@ -82,20 +113,21 @@ describe Gitlab::Git::Repository, seed_helper: true do
it { is_expected.to include("master") }
it { is_expected.not_to include("branch-from-space") }
- context 'with gitaly enabled' do
- before { stub_gitaly }
-
- it 'gets the branch names from GitalyClient' do
- expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names)
- subject
- end
-
- it 'wraps GRPC exceptions' do
- expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names).
- and_raise(GRPC::Unknown)
- expect { subject }.to raise_error(Gitlab::Git::CommandError)
- end
- end
+ # TODO: Uncomment when feature is reenabled
+ # context 'with gitaly enabled' do
+ # before { stub_gitaly }
+ #
+ # it 'gets the branch names from GitalyClient' do
+ # expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names)
+ # subject
+ # end
+ #
+ # it 'wraps GRPC exceptions' do
+ # expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names).
+ # and_raise(GRPC::Unknown)
+ # expect { subject }.to raise_error(Gitlab::Git::CommandError)
+ # end
+ # end
end
describe '#tag_names' do
@@ -113,20 +145,21 @@ describe Gitlab::Git::Repository, seed_helper: true do
it { is_expected.to include("v1.0.0") }
it { is_expected.not_to include("v5.0.0") }
- context 'with gitaly enabled' do
- before { stub_gitaly }
-
- it 'gets the tag names from GitalyClient' do
- expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names)
- subject
- end
-
- it 'wraps GRPC exceptions' do
- expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names).
- and_raise(GRPC::Unknown)
- expect { subject }.to raise_error(Gitlab::Git::CommandError)
- end
- end
+ # TODO: Uncomment when feature is reenabled
+ # context 'with gitaly enabled' do
+ # before { stub_gitaly }
+ #
+ # it 'gets the tag names from GitalyClient' do
+ # expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names)
+ # subject
+ # end
+ #
+ # it 'wraps GRPC exceptions' do
+ # expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names).
+ # and_raise(GRPC::Unknown)
+ # expect { subject }.to raise_error(Gitlab::Git::CommandError)
+ # end
+ # end
end
shared_examples 'archive check' do |extenstion|
diff --git a/spec/lib/gitlab/git/rev_list_spec.rb b/spec/lib/gitlab/git/rev_list_spec.rb
index d48629a296d..78894ba9409 100644
--- a/spec/lib/gitlab/git/rev_list_spec.rb
+++ b/spec/lib/gitlab/git/rev_list_spec.rb
@@ -3,58 +3,54 @@ require 'spec_helper'
describe Gitlab::Git::RevList, lib: true do
let(:project) { create(:project, :repository) }
- context "validations" do
- described_class::ALLOWED_VARIABLES.each do |var|
- context var do
- it "accepts values starting with the project repo path" do
- env = { var => "#{project.repository.path_to_repo}/objects" }
- rev_list = described_class.new('oldrev', 'newrev', project: project, env: env)
-
- expect(rev_list).to be_valid
- end
-
- it "rejects values starting not with the project repo path" do
- env = { var => "/some/other/path" }
- rev_list = described_class.new('oldrev', 'newrev', project: project, env: env)
-
- expect(rev_list).not_to be_valid
- end
-
- it "rejects values containing the project repo path but not starting with it" do
- env = { var => "/some/other/path/#{project.repository.path_to_repo}" }
- rev_list = described_class.new('oldrev', 'newrev', project: project, env: env)
-
- expect(rev_list).not_to be_valid
- end
-
- it "ignores nil values" do
- env = { var => nil }
- rev_list = described_class.new('oldrev', 'newrev', project: project, env: env)
-
- expect(rev_list).to be_valid
- end
- end
- end
+ before do
+ expect(Gitlab::Git::Env).to receive(:all).and_return({
+ GIT_OBJECT_DIRECTORY: 'foo',
+ GIT_ALTERNATE_OBJECT_DIRECTORIES: 'bar'
+ })
end
- context "#execute" do
- let(:env) { { "GIT_OBJECT_DIRECTORY" => project.repository.path_to_repo } }
- let(:rev_list) { Gitlab::Git::RevList.new('oldrev', 'newrev', project: project, env: env) }
-
- it "calls out to `popen` without environment variables if the record is invalid" do
- allow(rev_list).to receive(:valid?).and_return(false)
-
- expect(Open3).to receive(:popen3).with(hash_excluding(env), any_args)
-
- rev_list.execute
+ context "#new_refs" do
+ let(:rev_list) { Gitlab::Git::RevList.new(newrev: 'newrev', path_to_repo: project.repository.path_to_repo) }
+
+ it 'calls out to `popen`' do
+ expect(Gitlab::Popen).to receive(:popen).with([
+ Gitlab.config.git.bin_path,
+ "--git-dir=#{project.repository.path_to_repo}",
+ 'rev-list',
+ 'newrev',
+ '--not',
+ '--all'
+ ],
+ nil,
+ {
+ 'GIT_OBJECT_DIRECTORY' => 'foo',
+ 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => 'bar'
+ }).and_return(["sha1\nsha2", 0])
+
+ expect(rev_list.new_refs).to eq(%w[sha1 sha2])
end
+ end
- it "calls out to `popen` with environment variables if the record is valid" do
- allow(rev_list).to receive(:valid?).and_return(true)
-
- expect(Open3).to receive(:popen3).with(hash_including(env), any_args)
-
- rev_list.execute
+ context "#missed_ref" do
+ let(:rev_list) { Gitlab::Git::RevList.new(oldrev: 'oldrev', newrev: 'newrev', path_to_repo: project.repository.path_to_repo) }
+
+ it 'calls out to `popen`' do
+ expect(Gitlab::Popen).to receive(:popen).with([
+ Gitlab.config.git.bin_path,
+ "--git-dir=#{project.repository.path_to_repo}",
+ 'rev-list',
+ '--max-count=1',
+ 'oldrev',
+ '^newrev'
+ ],
+ nil,
+ {
+ 'GIT_OBJECT_DIRECTORY' => 'foo',
+ 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => 'bar'
+ }).and_return(["sha1\nsha2", 0])
+
+ expect(rev_list.missed_ref).to eq(%w[sha1 sha2])
end
end
end
diff --git a/spec/migrations/migrate_user_project_view_spec.rb b/spec/migrations/migrate_user_project_view_spec.rb
new file mode 100644
index 00000000000..dacaa834aa9
--- /dev/null
+++ b/spec/migrations/migrate_user_project_view_spec.rb
@@ -0,0 +1,17 @@
+# encoding: utf-8
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20170406142253_migrate_user_project_view.rb')
+
+describe MigrateUserProjectView do
+ let(:migration) { described_class.new }
+ let!(:user) { create(:user, project_view: 'readme') }
+
+ describe '#up' do
+ it 'updates project view setting with new value' do
+ migration.up
+
+ expect(user.reload.project_view).to eq('files')
+ end
+ end
+end
diff --git a/spec/migrations/schema_spec.rb b/spec/migrations/schema_spec.rb
new file mode 100644
index 00000000000..e132529d8d8
--- /dev/null
+++ b/spec/migrations/schema_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+# Check consistency of db/schema.rb version, migrations' timestamps, and the latest migration timestamp
+# stored in the database's schema_migrations table.
+
+describe ActiveRecord::Schema do
+ let(:latest_migration_timestamp) do
+ migrations = Dir[Rails.root.join('db', 'migrate', '*'), Rails.root.join('db', 'post_migrate', '*')]
+ migrations.map { |migration| File.basename(migration).split('_').first.to_i }.max
+ end
+
+ it '> schema version equals last migration timestamp' do
+ defined_schema_version = File.open(Rails.root.join('db', 'schema.rb')) do |file|
+ file.find { |line| line =~ /ActiveRecord::Schema.define/ }
+ end.match(/(\d+)/)[0].to_i
+
+ expect(defined_schema_version).to eq(latest_migration_timestamp)
+ end
+
+ it '> schema version should equal the latest migration timestamp stored in schema_migrations table' do
+ expect(latest_migration_timestamp).to eq(ActiveRecord::Migrator.current_version.to_i)
+ end
+end
diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb
index 4e71597521d..ced93c8f762 100644
--- a/spec/models/abuse_report_spec.rb
+++ b/spec/models/abuse_report_spec.rb
@@ -29,7 +29,8 @@ RSpec.describe AbuseReport, type: :model do
it 'lets a worker delete the user' do
expect(DeleteUserWorker).to receive(:perform_async).with(user.id, subject.user.id,
- delete_solo_owned_groups: true)
+ delete_solo_owned_groups: true,
+ hard_delete: true)
subject.remove_user(deleted_by: user)
end
diff --git a/spec/models/blob_spec.rb b/spec/models/blob_spec.rb
index 0f29766db41..e5dd57fc4bb 100644
--- a/spec/models/blob_spec.rb
+++ b/spec/models/blob_spec.rb
@@ -55,13 +55,13 @@ describe Blob do
describe '#pdf?' do
it 'is falsey when file extension is not .pdf' do
- git_blob = double(name: 'git_blob.txt')
+ git_blob = Gitlab::Git::Blob.new(name: 'git_blob.txt')
expect(described_class.decorate(git_blob)).not_to be_pdf
end
it 'is truthy when file extension is .pdf' do
- git_blob = double(name: 'git_blob.pdf')
+ git_blob = Gitlab::Git::Blob.new(name: 'git_blob.pdf')
expect(described_class.decorate(git_blob)).to be_pdf
end
@@ -140,7 +140,7 @@ describe Blob do
stl?: false
)
- described_class.decorate(double).tap do |blob|
+ described_class.decorate(Gitlab::Git::Blob.new({})).tap do |blob|
allow(blob).to receive_messages(overrides)
end
end
@@ -158,7 +158,7 @@ describe Blob do
it 'handles SVGs' do
blob = stubbed_blob(text?: true, svg?: true)
- expect(blob.to_partial_path(project)).to eq 'image'
+ expect(blob.to_partial_path(project)).to eq 'svg'
end
it 'handles images' do
@@ -167,7 +167,7 @@ describe Blob do
end
it 'handles text' do
- blob = stubbed_blob(text?: true)
+ blob = stubbed_blob(text?: true, name: 'test.txt')
expect(blob.to_partial_path(project)).to eq 'text'
end
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 980a1b70ef5..ce31c8ed94c 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -389,31 +389,32 @@ eos
end
end
- describe '#raw_diffs' do
- context 'Gitaly commit_raw_diffs feature enabled' do
- before do
- allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:commit_raw_diffs).and_return(true)
- end
-
- context 'when a truthy deltas_only is not passed to args' do
- it 'fetches diffs from Gitaly server' do
- expect(Gitlab::GitalyClient::Commit).to receive(:diff_from_parent).
- with(commit)
-
- commit.raw_diffs
- end
- end
-
- context 'when a truthy deltas_only is passed to args' do
- it 'fetches diffs using Rugged' do
- opts = { deltas_only: true }
-
- expect(Gitlab::GitalyClient::Commit).not_to receive(:diff_from_parent)
- expect(commit.raw).to receive(:diffs).with(opts)
-
- commit.raw_diffs(opts)
- end
- end
- end
- end
+ # describe '#raw_diffs' do
+ # TODO: Uncomment when feature is reenabled
+ # context 'Gitaly commit_raw_diffs feature enabled' do
+ # before do
+ # allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:commit_raw_diffs).and_return(true)
+ # end
+ #
+ # context 'when a truthy deltas_only is not passed to args' do
+ # it 'fetches diffs from Gitaly server' do
+ # expect(Gitlab::GitalyClient::Commit).to receive(:diff_from_parent).
+ # with(commit)
+ #
+ # commit.raw_diffs
+ # end
+ # end
+ #
+ # context 'when a truthy deltas_only is passed to args' do
+ # it 'fetches diffs using Rugged' do
+ # opts = { deltas_only: true }
+ #
+ # expect(Gitlab::GitalyClient::Commit).not_to receive(:diff_from_parent)
+ # expect(commit.raw).to receive(:diffs).with(opts)
+ #
+ # commit.raw_diffs(opts)
+ # end
+ # end
+ # end
+ # end
end
diff --git a/spec/models/concerns/ignorable_column_spec.rb b/spec/models/concerns/ignorable_column_spec.rb
new file mode 100644
index 00000000000..dba9fe43327
--- /dev/null
+++ b/spec/models/concerns/ignorable_column_spec.rb
@@ -0,0 +1,38 @@
+require 'spec_helper'
+
+describe IgnorableColumn do
+ let :base_class do
+ Class.new do
+ def self.columns
+ # This method does not have access to "double"
+ [Struct.new(:name).new('id'), Struct.new(:name).new('title')]
+ end
+ end
+ end
+
+ let :model do
+ Class.new(base_class) do
+ include IgnorableColumn
+ end
+ end
+
+ describe '.columns' do
+ it 'returns the columns, excluding the ignored ones' do
+ model.ignore_column(:title)
+
+ expect(model.columns.map(&:name)).to eq(%w(id))
+ end
+ end
+
+ describe '.ignored_columns' do
+ it 'returns a Set' do
+ expect(model.ignored_columns).to be_an_instance_of(Set)
+ end
+
+ it 'returns the names of the ignored columns' do
+ model.ignore_column(:title)
+
+ expect(model.ignored_columns).to eq(Set.new(%w(title)))
+ end
+ end
+end
diff --git a/spec/models/container_repository_spec.rb b/spec/models/container_repository_spec.rb
index f7ee0b57072..eff41d85972 100644
--- a/spec/models/container_repository_spec.rb
+++ b/spec/models/container_repository_spec.rb
@@ -4,7 +4,7 @@ describe ContainerRepository do
let(:group) { create(:group, name: 'group') }
let(:project) { create(:project, path: 'test', group: group) }
- let(:container_repository) do
+ let(:repository) do
create(:container_repository, name: 'my_image', project: project)
end
@@ -23,48 +23,58 @@ describe ContainerRepository do
describe 'associations' do
it 'belongs to the project' do
- expect(container_repository).to belong_to(:project)
+ expect(repository).to belong_to(:project)
end
end
describe '#tag' do
it 'has a test tag' do
- expect(container_repository.tag('test')).not_to be_nil
+ expect(repository.tag('test')).not_to be_nil
end
end
describe '#path' do
- it 'returns a full path to the repository' do
- expect(container_repository.path).to eq('group/test/my_image')
+ context 'when project path does not contain uppercase letters' do
+ it 'returns a full path to the repository' do
+ expect(repository.path).to eq('group/test/my_image')
+ end
+ end
+
+ context 'when path contains uppercase letters' do
+ let(:project) { create(:project, path: 'MY_PROJECT', group: group) }
+
+ it 'returns a full path without capital letters' do
+ expect(repository.path).to eq('group/my_project/my_image')
+ end
end
end
describe '#manifest' do
- subject { container_repository.manifest }
-
- it { is_expected.not_to be_nil }
+ it 'returns non-empty manifest' do
+ expect(repository.manifest).not_to be_nil
+ end
end
describe '#valid?' do
- subject { container_repository.valid? }
-
- it { is_expected.to be_truthy }
+ it 'is a valid repository' do
+ expect(repository).to be_valid
+ end
end
describe '#tags' do
- subject { container_repository.tags }
-
- it { is_expected.not_to be_empty }
+ it 'returns non-empty tags list' do
+ expect(repository.tags).not_to be_empty
+ end
end
describe '#has_tags?' do
it 'has tags' do
- expect(container_repository).to have_tags
+ expect(repository).to have_tags
end
end
describe '#delete_tags!' do
- let(:container_repository) do
+ let(:repository) do
create(:container_repository, name: 'my_image',
tags: %w[latest rc1],
project: project)
@@ -72,21 +82,36 @@ describe ContainerRepository do
context 'when action succeeds' do
it 'returns status that indicates success' do
- expect(container_repository.client)
+ expect(repository.client)
.to receive(:delete_repository_tag)
.and_return(true)
- expect(container_repository.delete_tags!).to be_truthy
+ expect(repository.delete_tags!).to be_truthy
end
end
context 'when action fails' do
it 'returns status that indicates failure' do
- expect(container_repository.client)
+ expect(repository.client)
.to receive(:delete_repository_tag)
.and_return(false)
- expect(container_repository.delete_tags!).to be_falsey
+ expect(repository.delete_tags!).to be_falsey
+ end
+ end
+ end
+
+ describe '#location' do
+ context 'when registry is running on a custom port' do
+ before do
+ stub_container_registry_config(enabled: true,
+ api_url: 'http://registry.gitlab:5000',
+ host_port: 'registry.gitlab:5000')
+ end
+
+ it 'returns a full location of the repository' do
+ expect(repository.location)
+ .to eq 'registry.gitlab:5000/group/test/my_image'
end
end
end
@@ -102,7 +127,7 @@ describe ContainerRepository do
context 'when repository is not a root repository' do
it 'returns false' do
- expect(container_repository).not_to be_root_repository
+ expect(repository).not_to be_root_repository
end
end
end
diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb
index fb80b74b226..f32b6b99b3d 100644
--- a/spec/models/diff_note_spec.rb
+++ b/spec/models/diff_note_spec.rb
@@ -155,6 +155,23 @@ describe DiffNote, models: true do
end
end
+ describe '#latest_merge_request_diff' do
+ context 'when active' do
+ it 'returns the current merge request diff' do
+ expect(subject.latest_merge_request_diff).to eq(merge_request.merge_request_diff)
+ end
+ end
+
+ context 'when outdated' do
+ let!(:old_merge_request_diff) { merge_request.merge_request_diff }
+ let!(:new_merge_request_diff) { merge_request.merge_request_diffs.create(diff_refs: commit.diff_refs) }
+
+ it 'returns the latest merge request diff that this diff note applied to' do
+ expect(subject.latest_merge_request_diff).to eq(old_merge_request_diff)
+ end
+ end
+ end
+
describe "creation" do
describe "updating of position" do
context "when noteable is a commit" do
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index af7753caba6..070716e859a 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -110,17 +110,18 @@ describe Environment, models: true do
end
end
- context 'Gitaly find_ref_name feature enabled' do
- before do
- allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:find_ref_name).and_return(true)
- end
-
- it 'calls GitalyClient' do
- expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:find_ref_name)
-
- environment.first_deployment_for(commit)
- end
- end
+ # TODO: Uncomment when feature is reenabled
+ # context 'Gitaly find_ref_name feature enabled' do
+ # before do
+ # allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:find_ref_name).and_return(true)
+ # end
+ #
+ # it 'calls GitalyClient' do
+ # expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:find_ref_name)
+ #
+ # environment.first_deployment_for(commit)
+ # end
+ # end
end
describe '#environment_type' do
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 4bdd46a581d..d057c9cf6e9 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -134,15 +134,6 @@ describe Issue, models: true do
end
end
- describe '#is_being_reassigned?' do
- it 'returns issues assigned to user' do
- user = create(:user)
- create_list(:issue, 2, assignee: user)
-
- expect(Issue.open_for(user).count).to eq 2
- end
- end
-
describe '#closed_by_merge_requests' do
let(:project) { create(:project, :repository) }
let(:issue) { create(:issue, project: project)}
diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb
index a9139f7d4ab..80ca19acdda 100644
--- a/spec/models/label_spec.rb
+++ b/spec/models/label_spec.rb
@@ -42,11 +42,27 @@ describe Label, models: true do
end
end
+ describe '#color' do
+ it 'strips color' do
+ label = described_class.new(color: ' #abcdef ')
+ label.valid?
+
+ expect(label.color).to eq('#abcdef')
+ end
+ end
+
describe '#title' do
it 'sanitizes title' do
label = described_class.new(title: '<b>foo & bar?</b>')
expect(label.title).to eq('foo & bar?')
end
+
+ it 'strips title' do
+ label = described_class.new(title: ' label ')
+ label.valid?
+
+ expect(label.title).to eq('label')
+ end
end
describe 'priorization' do
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 3c4bf3f4ddb..557ea97b008 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -622,12 +622,22 @@ describe Note, models: true do
describe 'expiring ETag cache' do
let(:note) { build(:note_on_issue) }
- it "expires cache for note's issue when note is saved" do
+ def expect_expiration(note)
expect_any_instance_of(Gitlab::EtagCaching::Store)
.to receive(:touch)
.with("/#{note.project.namespace.to_param}/#{note.project.to_param}/noteable/issue/#{note.noteable.id}/notes")
+ end
+
+ it "expires cache for note's issue when note is saved" do
+ expect_expiration(note)
note.save!
end
+
+ it "expires cache for note's issue when note is destroyed" do
+ expect_expiration(note)
+
+ note.destroy!
+ end
end
end
diff --git a/spec/models/project_services/chat_notification_service_spec.rb b/spec/models/project_services/chat_notification_service_spec.rb
index c98e7ee14fd..592c90cda36 100644
--- a/spec/models/project_services/chat_notification_service_spec.rb
+++ b/spec/models/project_services/chat_notification_service_spec.rb
@@ -1,11 +1,29 @@
require 'spec_helper'
describe ChatNotificationService, models: true do
- describe "Associations" do
+ describe 'Associations' do
before do
allow(subject).to receive(:activated?).and_return(true)
end
it { is_expected.to validate_presence_of :webhook }
end
+
+ describe '#can_test?' do
+ context 'with empty repository' do
+ it 'returns false' do
+ subject.project = create(:empty_project, :empty_repo)
+
+ expect(subject.can_test?).to be false
+ end
+ end
+
+ context 'with repository' do
+ it 'returns true' do
+ subject.project = create(:project)
+
+ expect(subject.can_test?).to be true
+ end
+ end
+ end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index d805e65b3c6..74d5ebc6db0 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -171,6 +171,27 @@ describe Repository, models: true do
end
end
+ describe '#commits' do
+ it 'sets follow when path is a single path' do
+ expect(Gitlab::Git::Commit).to receive(:where).with(a_hash_including(follow: true)).and_call_original.twice
+
+ repository.commits('master', path: 'README.md')
+ repository.commits('master', path: ['README.md'])
+ end
+
+ it 'does not set follow when path is multiple paths' do
+ expect(Gitlab::Git::Commit).to receive(:where).with(a_hash_including(follow: false)).and_call_original
+
+ repository.commits('master', path: ['README.md', 'CHANGELOG'])
+ end
+
+ it 'does not set follow when there are no paths' do
+ expect(Gitlab::Git::Commit).to receive(:where).with(a_hash_including(follow: false)).and_call_original
+
+ repository.commits('master')
+ end
+ end
+
describe '#find_commits_by_message' do
it 'returns commits with messages containing a given string' do
commit_ids = repository.find_commits_by_message('submodule').map(&:id)
@@ -1259,7 +1280,6 @@ describe Repository, models: true do
:changelog,
:license,
:contributing,
- :version,
:gitignore,
:koding,
:gitlab_ci,
@@ -1283,8 +1303,6 @@ describe Repository, models: true do
describe '#after_import' do
it 'flushes and builds the cache' do
expect(repository).to receive(:expire_content_cache)
- expect(repository).to receive(:expire_tags_cache)
- expect(repository).to receive(:expire_branches_cache)
repository.after_import
end
@@ -1831,16 +1849,17 @@ describe Repository, models: true do
end
end
- describe '#is_ancestor?' do
- context 'Gitaly is_ancestor feature enabled' do
- it 'asks Gitaly server if it\'s an ancestor' do
- commit = repository.commit
- allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:is_ancestor).and_return(true)
- expect(Gitlab::GitalyClient::Commit).to receive(:is_ancestor).
- with(repository.raw_repository, commit.id, commit.id).and_return(true)
-
- expect(repository.is_ancestor?(commit.id, commit.id)).to be true
- end
- end
- end
+ # TODO: Uncomment when feature is reenabled
+ # describe '#is_ancestor?' do
+ # context 'Gitaly is_ancestor feature enabled' do
+ # it 'asks Gitaly server if it\'s an ancestor' do
+ # commit = repository.commit
+ # allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:is_ancestor).and_return(true)
+ # expect(Gitlab::GitalyClient::Commit).to receive(:is_ancestor).
+ # with(repository.raw_repository, commit.id, commit.id).and_return(true)
+ #
+ # expect(repository.is_ancestor?(commit.id, commit.id)).to be true
+ # end
+ # end
+ # end
end
diff --git a/spec/models/spam_log_spec.rb b/spec/models/spam_log_spec.rb
index c4ec7625cb0..838fba6c92d 100644
--- a/spec/models/spam_log_spec.rb
+++ b/spec/models/spam_log_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe SpamLog, models: true do
+ let(:admin) { create(:admin) }
+
describe 'associations' do
it { is_expected.to belong_to(:user) }
end
@@ -13,13 +15,18 @@ describe SpamLog, models: true do
it 'blocks the user' do
spam_log = build(:spam_log)
- expect { spam_log.remove_user }.to change { spam_log.user.blocked? }.to(true)
+ expect { spam_log.remove_user(deleted_by: admin) }.to change { spam_log.user.blocked? }.to(true)
end
it 'removes the user' do
spam_log = build(:spam_log)
+ user = spam_log.user
+
+ Sidekiq::Testing.inline! do
+ spam_log.remove_user(deleted_by: admin)
+ end
- expect { spam_log.remove_user }.to change { User.count }.by(-1)
+ expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 6f7b9c2388a..9de16c41e94 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -315,7 +315,7 @@ describe User, models: true do
end
describe "Respond to" do
- it { is_expected.to respond_to(:is_admin?) }
+ it { is_expected.to respond_to(:admin?) }
it { is_expected.to respond_to(:name) }
it { is_expected.to respond_to(:private_token) }
it { is_expected.to respond_to(:external?) }
@@ -586,7 +586,7 @@ describe User, models: true do
describe 'normal user' do
let(:user) { create(:user, name: 'John Smith') }
- it { expect(user.is_admin?).to be_falsey }
+ it { expect(user.admin?).to be_falsey }
it { expect(user.require_ssh_key?).to be_truthy }
it { expect(user.can_create_group?).to be_truthy }
it { expect(user.can_create_project?).to be_truthy }
diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb
index 5c34ff04152..2077c14ff7a 100644
--- a/spec/policies/group_policy_spec.rb
+++ b/spec/policies/group_policy_spec.rb
@@ -22,7 +22,8 @@ describe GroupPolicy, models: true do
:admin_group,
:admin_namespace,
:admin_group_member,
- :change_visibility_level
+ :change_visibility_level,
+ :create_subgroup
]
end
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index eed45d37444..4be67df5a00 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -153,6 +153,22 @@ describe API::Internal, api: true do
project.team << [user, :developer]
end
+ context 'with env passed as a JSON' do
+ it 'sets env in RequestStore' do
+ expect(Gitlab::Git::Env).to receive(:set).with({
+ 'GIT_OBJECT_DIRECTORY' => 'foo',
+ 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => 'bar'
+ })
+
+ push(key, project.wiki, env: {
+ GIT_OBJECT_DIRECTORY: 'foo',
+ GIT_ALTERNATE_OBJECT_DIRECTORIES: 'bar'
+ }.to_json)
+
+ expect(response).to have_http_status(200)
+ end
+ end
+
context "git push with project.wiki" do
it 'responds with success' do
push(key, project.wiki)
@@ -463,7 +479,7 @@ describe API::Internal, api: true do
)
end
- def push(key, project, protocol = 'ssh')
+ def push(key, project, protocol = 'ssh', env: nil)
post(
api("/internal/allowed"),
changes: 'd14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/master',
@@ -471,7 +487,8 @@ describe API::Internal, api: true do
project: project.repository.path_to_repo,
action: 'git-receive-pack',
secret_token: secret_token,
- protocol: protocol
+ protocol: protocol,
+ env: env
)
end
diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb
index b1f8c249092..b1603233f9e 100644
--- a/spec/requests/api/project_hooks_spec.rb
+++ b/spec/requests/api/project_hooks_spec.rb
@@ -22,8 +22,8 @@ describe API::ProjectHooks, 'ProjectHooks', api: true do
context "authorized user" do
it "returns project hooks" do
get api("/projects/#{project.id}/hooks", user)
- expect(response).to have_http_status(200)
+ 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)
@@ -43,6 +43,7 @@ describe API::ProjectHooks, 'ProjectHooks', api: true do
context "unauthorized user" do
it "does not access project hooks" do
get api("/projects/#{project.id}/hooks", user3)
+
expect(response).to have_http_status(403)
end
end
@@ -52,6 +53,7 @@ describe API::ProjectHooks, 'ProjectHooks', api: true do
context "authorized user" do
it "returns a project hook" do
get api("/projects/#{project.id}/hooks/#{hook.id}", user)
+
expect(response).to have_http_status(200)
expect(json_response['url']).to eq(hook.url)
expect(json_response['issues_events']).to eq(hook.issues_events)
@@ -67,6 +69,7 @@ describe API::ProjectHooks, 'ProjectHooks', api: true do
it "returns a 404 error if hook id is not available" do
get api("/projects/#{project.id}/hooks/1234", user)
+
expect(response).to have_http_status(404)
end
end
@@ -88,7 +91,8 @@ describe API::ProjectHooks, 'ProjectHooks', api: true do
it "adds hook to project" do
expect do
post api("/projects/#{project.id}/hooks", user),
- url: "http://example.com", issues_events: true, wiki_page_events: true
+ url: "http://example.com", issues_events: true, wiki_page_events: true,
+ job_events: true
end.to change {project.hooks.count}.by(1)
expect(response).to have_http_status(201)
@@ -98,7 +102,7 @@ describe API::ProjectHooks, 'ProjectHooks', api: true do
expect(json_response['merge_requests_events']).to eq(false)
expect(json_response['tag_push_events']).to eq(false)
expect(json_response['note_events']).to eq(false)
- expect(json_response['job_events']).to eq(false)
+ expect(json_response['job_events']).to eq(true)
expect(json_response['pipeline_events']).to eq(false)
expect(json_response['wiki_page_events']).to eq(true)
expect(json_response['enable_ssl_verification']).to eq(true)
@@ -136,7 +140,8 @@ describe API::ProjectHooks, 'ProjectHooks', api: true do
describe "PUT /projects/:id/hooks/:hook_id" do
it "updates an existing project hook" do
put api("/projects/#{project.id}/hooks/#{hook.id}", user),
- url: 'http://example.org', push_events: false
+ url: 'http://example.org', push_events: false, job_events: true
+
expect(response).to have_http_status(200)
expect(json_response['url']).to eq('http://example.org')
expect(json_response['issues_events']).to eq(hook.issues_events)
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 2e291eb3cea..74bc4847247 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -1076,6 +1076,13 @@ describe API::Projects, :api do
before { project_member3 }
before { project_member2 }
+ it 'returns 400 when nothing sent' do
+ project_param = {}
+ put api("/projects/#{project.id}", user), project_param
+ expect(response).to have_http_status(400)
+ expect(json_response['error']).to match('at least one parameter must be provided')
+ end
+
context 'when unauthenticated' do
it 'returns authentication error' do
project_param = { name: 'bar' }
diff --git a/spec/requests/api/session_spec.rb b/spec/requests/api/session_spec.rb
index 28fab2011a5..393bf076616 100644
--- a/spec/requests/api/session_spec.rb
+++ b/spec/requests/api/session_spec.rb
@@ -13,7 +13,7 @@ describe API::Session, api: true do
expect(json_response['email']).to eq(user.email)
expect(json_response['private_token']).to eq(user.private_token)
- expect(json_response['is_admin']).to eq(user.is_admin?)
+ expect(json_response['is_admin']).to eq(user.admin?)
expect(json_response['can_create_project']).to eq(user.can_create_project?)
expect(json_response['can_create_group']).to eq(user.can_create_group?)
end
@@ -37,7 +37,7 @@ describe API::Session, api: true do
expect(json_response['email']).to eq user.email
expect(json_response['private_token']).to eq user.private_token
- expect(json_response['is_admin']).to eq user.is_admin?
+ expect(json_response['is_admin']).to eq user.admin?
expect(json_response['can_create_project']).to eq user.can_create_project?
expect(json_response['can_create_group']).to eq user.can_create_group?
end
@@ -50,7 +50,7 @@ describe API::Session, api: true do
expect(json_response['email']).to eq user.email
expect(json_response['private_token']).to eq user.private_token
- expect(json_response['is_admin']).to eq user.is_admin?
+ expect(json_response['is_admin']).to eq user.admin?
expect(json_response['can_create_project']).to eq user.can_create_project?
expect(json_response['can_create_group']).to eq user.can_create_group?
end
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 4baccacd448..a3de022d242 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -484,7 +484,7 @@ describe 'project routing' do
end
it 'to #list' do
- expect(get('/gitlab/gitlabhq/files/master.json')).to route_to('projects/find_file#list', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json')
+ expect(get('/gitlab/gitlabhq/files/master.json')).to route_to('projects/find_file#list', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master.json')
end
end
diff --git a/spec/serializers/build_serializer_spec.rb b/spec/serializers/build_serializer_spec.rb
index 3cc791bca50..7f1abecfafe 100644
--- a/spec/serializers/build_serializer_spec.rb
+++ b/spec/serializers/build_serializer_spec.rb
@@ -38,7 +38,7 @@ describe BuildSerializer do
expect(subject[:text]).to eq(status.text)
expect(subject[:label]).to eq(status.label)
expect(subject[:icon]).to eq(status.icon)
- expect(subject[:favicon]).to eq(status.favicon)
+ expect(subject[:favicon]).to eq("/assets/ci_favicons/#{status.favicon}.ico")
end
end
end
diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb
index f6249ab4664..ecde45a6d44 100644
--- a/spec/serializers/pipeline_serializer_spec.rb
+++ b/spec/serializers/pipeline_serializer_spec.rb
@@ -144,7 +144,7 @@ describe PipelineSerializer do
expect(subject[:text]).to eq(status.text)
expect(subject[:label]).to eq(status.label)
expect(subject[:icon]).to eq(status.icon)
- expect(subject[:favicon]).to eq(status.favicon)
+ expect(subject[:favicon]).to eq("/assets/ci_favicons/#{status.favicon}.ico")
end
end
end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 617c8eaf3d5..989fd90cda9 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -1181,6 +1181,22 @@ describe NotificationService, services: true do
should_not_email(@u_disabled)
end
end
+
+ describe '#project_exported' do
+ it do
+ notification.project_exported(project, @u_disabled)
+
+ should_only_email(@u_disabled)
+ end
+ end
+
+ describe '#project_not_exported' do
+ it do
+ notification.project_not_exported(project, @u_disabled, ['error'])
+
+ should_only_email(@u_disabled)
+ end
+ end
end
describe 'GroupMember' do
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index 62f21049b0b..7a07ea618c0 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -144,6 +144,20 @@ describe Projects::CreateService, '#execute', services: true do
end
end
+ context 'when a bad service template is created' do
+ before do
+ create(:service, type: 'DroneCiService', project: nil, template: true, active: true)
+ end
+
+ it 'reports an error in the imported project' do
+ opts[:import_url] = 'http://www.gitlab.com/gitlab-org/gitlab-ce'
+ project = create_project(user, opts)
+
+ expect(project.errors.full_messages_for(:base).first).to match /Unable to save project. Error: Unable to save DroneCiService/
+ expect(project.services.count).to eq 0
+ end
+ end
+
def create_project(user, opts)
Projects::CreateService.new(user, opts).execute
end
diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb
index 09cfa36b3b9..852a4ac852f 100644
--- a/spec/services/projects/import_service_spec.rb
+++ b/spec/services/projects/import_service_spec.rb
@@ -54,6 +54,15 @@ describe Projects::ImportService, services: true do
expect(result[:status]).to eq :error
expect(result[:message]).to eq "Error importing repository #{project.import_url} into #{project.path_with_namespace} - Failed to import the repository"
end
+
+ it 'does not remove the GitHub remote' do
+ expect_any_instance_of(Repository).to receive(:fetch_remote).and_return(true)
+ expect_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_return(true)
+
+ subject.execute
+
+ expect(project.repository.raw_repository.remote_names).to include('github')
+ end
end
context 'with a non Github repository' do
diff --git a/spec/services/search/global_service_spec.rb b/spec/services/search/global_service_spec.rb
index 2531607acad..cbf4f56213d 100644
--- a/spec/services/search/global_service_spec.rb
+++ b/spec/services/search/global_service_spec.rb
@@ -40,27 +40,6 @@ describe Search::GlobalService, services: true do
expect(results.objects('projects')).to match_array [found_project]
end
-
- context 'nested group' do
- let!(:nested_group) { create(:group, :nested) }
- let!(:project) { create(:empty_project, namespace: nested_group) }
-
- before do
- project.add_master(user)
- end
-
- it 'returns result from nested group' do
- results = Search::GlobalService.new(user, search: project.path).execute
-
- expect(results.objects('projects')).to match_array [project]
- end
-
- it 'returns result from descendants when search inside group' do
- results = Search::GlobalService.new(user, search: project.path, group_id: nested_group.parent).execute
-
- expect(results.objects('projects')).to match_array [project]
- end
- end
end
end
end
diff --git a/spec/services/search/group_service_spec.rb b/spec/services/search/group_service_spec.rb
new file mode 100644
index 00000000000..38f264f6e7b
--- /dev/null
+++ b/spec/services/search/group_service_spec.rb
@@ -0,0 +1,40 @@
+require 'spec_helper'
+
+describe Search::GroupService, services: true do
+ shared_examples_for 'group search' do
+ context 'finding projects by name' do
+ let(:user) { create(:user) }
+ let(:term) { "Project Name" }
+ let(:nested_group) { create(:group, :nested) }
+
+ # These projects shouldn't be found
+ let!(:outside_project) { create(:empty_project, :public, name: "Outside #{term}") }
+ let!(:private_project) { create(:empty_project, :private, namespace: nested_group, name: "Private #{term}" )}
+ let!(:other_project) { create(:empty_project, :public, namespace: nested_group, name: term.reverse) }
+
+ # These projects should be found
+ let!(:project1) { create(:empty_project, :internal, namespace: nested_group, name: "Inner #{term} 1") }
+ let!(:project2) { create(:empty_project, :internal, namespace: nested_group, name: "Inner #{term} 2") }
+ let!(:project3) { create(:empty_project, :internal, namespace: nested_group.parent, name: "Outer #{term}") }
+
+ let(:results) { Search::GroupService.new(user, search_group, search: term).execute }
+ subject { results.objects('projects') }
+
+ context 'in parent group' do
+ let(:search_group) { nested_group.parent }
+
+ it { is_expected.to match_array([project1, project2, project3]) }
+ end
+
+ context 'in subgroup' do
+ let(:search_group) { nested_group }
+
+ it { is_expected.to match_array([project1, project2]) }
+ end
+ end
+ end
+
+ describe 'basic search' do
+ include_examples 'group search'
+ end
+end
diff --git a/spec/services/users/build_service_spec.rb b/spec/services/users/build_service_spec.rb
new file mode 100644
index 00000000000..2a6bfc1b3a0
--- /dev/null
+++ b/spec/services/users/build_service_spec.rb
@@ -0,0 +1,55 @@
+require 'spec_helper'
+
+describe Users::BuildService, services: true do
+ describe '#execute' do
+ let(:params) do
+ { name: 'John Doe', username: 'jduser', email: 'jd@example.com', password: 'mydummypass' }
+ end
+
+ context 'with an admin user' do
+ let(:admin_user) { create(:admin) }
+ let(:service) { described_class.new(admin_user, params) }
+
+ it 'returns a valid user' do
+ expect(service.execute).to be_valid
+ end
+ end
+
+ context 'with non admin user' do
+ let(:user) { create(:user) }
+ let(:service) { described_class.new(user, params) }
+
+ it 'raises AccessDeniedError exception' do
+ expect { service.execute }.to raise_error Gitlab::Access::AccessDeniedError
+ end
+ end
+
+ context 'with nil user' do
+ let(:service) { described_class.new(nil, params) }
+
+ it 'returns a valid user' do
+ expect(service.execute).to be_valid
+ end
+
+ context 'when "send_user_confirmation_email" application setting is true' do
+ before do
+ stub_application_setting(send_user_confirmation_email: true, signup_enabled?: true)
+ end
+
+ it 'does not confirm the user' do
+ expect(service.execute).not_to be_confirmed
+ end
+ end
+
+ context 'when "send_user_confirmation_email" application setting is false' do
+ before do
+ stub_application_setting(send_user_confirmation_email: false, signup_enabled?: true)
+ end
+
+ it 'confirms the user' do
+ expect(service.execute).to be_confirmed
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/users/create_service_spec.rb b/spec/services/users/create_service_spec.rb
index a111aec2f89..75746278573 100644
--- a/spec/services/users/create_service_spec.rb
+++ b/spec/services/users/create_service_spec.rb
@@ -1,38 +1,6 @@
require 'spec_helper'
describe Users::CreateService, services: true do
- describe '#build' do
- let(:params) do
- { name: 'John Doe', username: 'jduser', email: 'jd@example.com', password: 'mydummypass' }
- end
-
- context 'with an admin user' do
- let(:admin_user) { create(:admin) }
- let(:service) { described_class.new(admin_user, params) }
-
- it 'returns a valid user' do
- expect(service.build).to be_valid
- end
- end
-
- context 'with non admin user' do
- let(:user) { create(:user) }
- let(:service) { described_class.new(user, params) }
-
- it 'raises AccessDeniedError exception' do
- expect { service.build }.to raise_error Gitlab::Access::AccessDeniedError
- end
- end
-
- context 'with nil user' do
- let(:service) { described_class.new(nil, params) }
-
- it 'returns a valid user' do
- expect(service.build).to be_valid
- end
- end
- end
-
describe '#execute' do
let(:admin_user) { create(:admin) }
@@ -185,40 +153,18 @@ describe Users::CreateService, services: true do
end
let(:service) { described_class.new(nil, params) }
- context 'when "send_user_confirmation_email" application setting is true' do
- before do
- current_application_settings = double(:current_application_settings, send_user_confirmation_email: true, signup_enabled?: true)
- allow(service).to receive(:current_application_settings).and_return(current_application_settings)
- end
-
- it 'does not confirm the user' do
- expect(service.execute).not_to be_confirmed
- end
- end
-
- context 'when "send_user_confirmation_email" application setting is false' do
- before do
- current_application_settings = double(:current_application_settings, send_user_confirmation_email: false, signup_enabled?: true)
- allow(service).to receive(:current_application_settings).and_return(current_application_settings)
- end
-
- it 'confirms the user' do
- expect(service.execute).to be_confirmed
- end
-
- it 'persists the given attributes' do
- user = service.execute
- user.reload
-
- expect(user).to have_attributes(
- name: params[:name],
- username: params[:username],
- email: params[:email],
- password: params[:password],
- created_by_id: nil,
- admin: false
- )
- end
+ it 'persists the given attributes' do
+ user = service.execute
+ user.reload
+
+ expect(user).to have_attributes(
+ name: params[:name],
+ username: params[:username],
+ email: params[:email],
+ password: params[:password],
+ created_by_id: nil,
+ admin: false
+ )
end
end
end
diff --git a/spec/services/users/destroy_service_spec.rb b/spec/services/users/destroy_service_spec.rb
index 43c18992d1a..4bc30018ebd 100644
--- a/spec/services/users/destroy_service_spec.rb
+++ b/spec/services/users/destroy_service_spec.rb
@@ -152,6 +152,12 @@ describe Users::DestroyService, services: true do
service.execute(user)
end
+
+ it 'does not run `MigrateToGhostUser` if hard_delete option is given' do
+ expect_any_instance_of(Users::MigrateToGhostUserService).not_to receive(:execute)
+
+ service.execute(user, hard_delete: true)
+ end
end
end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 4eb5b150af5..e67ad8f3455 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -9,8 +9,14 @@ require 'rspec/rails'
require 'shoulda/matchers'
require 'rspec/retry'
-if (ENV['RSPEC_PROFILING_POSTGRES_URL'] || ENV['RSPEC_PROFILING']) &&
- (!ENV.has_key?('CI') || ENV['CI_COMMIT_REF_NAME'] == 'master')
+rspec_profiling_is_configured =
+ ENV['RSPEC_PROFILING_POSTGRES_URL'] ||
+ ENV['RSPEC_PROFILING']
+branch_can_be_profiled =
+ ENV['CI_COMMIT_REF_NAME'] == 'master' ||
+ ENV['CI_COMMIT_REF_NAME'] =~ /rspec-profile/
+
+if rspec_profiling_is_configured && (!ENV.key?('CI') || branch_can_be_profiled)
require 'rspec_profiling/rspec'
end
@@ -59,6 +65,10 @@ RSpec.configure do |config|
TestEnv.init
end
+ config.after(:suite) do
+ TestEnv.cleanup
+ end
+
if ENV['CI']
# Retry only on feature specs that use JS
config.around :each, :js do |ex|
diff --git a/spec/features/discussion_comments_spec.rb b/spec/support/features/discussion_comments_shared_example.rb
index ae778118c5c..1a061ef069e 100644
--- a/spec/features/discussion_comments_spec.rb
+++ b/spec/support/features/discussion_comments_shared_example.rb
@@ -1,5 +1,3 @@
-require 'spec_helper'
-
shared_examples 'discussion comments' do |resource_name|
let(:form_selector) { '.js-main-target-form' }
let(:dropdown_selector) { "#{form_selector} .comment-type-dropdown" }
@@ -9,11 +7,9 @@ shared_examples 'discussion comments' do |resource_name|
let(:close_selector) { "#{form_selector} .btn-comment-and-close" }
let(:comments_selector) { '.timeline > .note.timeline-entry' }
- it 'should show a comment type toggle' do
+ it 'clicking "Comment" will post a comment' do
expect(page).to have_selector toggle_selector
- end
- it 'clicking "Comment" will post a comment' do
find("#{form_selector} .note-textarea").send_keys('a')
find(submit_selector).click
@@ -49,44 +45,29 @@ shared_examples 'discussion comments' do |resource_name|
find(toggle_selector).click
end
- it 'opens a comment type dropdown with "Comment" and "Start discussion"' do
+ it 'has a "Comment" item (selected by default) and "Start discussion" item' do
expect(page).to have_selector menu_selector
- end
-
- it 'has a "Comment" item' do
- menu = find(menu_selector)
-
- expect(menu).to have_content 'Comment'
- expect(menu).to have_content "Add a general comment to this #{resource_name}."
- end
- it 'has a "Start discussion" item' do
- menu = find(menu_selector)
-
- expect(menu).to have_content 'Start discussion'
- expect(menu).to have_content "Discuss a specific suggestion or question#{' that needs to be resolved' if resource_name == 'merge request'}."
- end
-
- it 'has the "Comment" item selected by default' do
find("#{menu_selector} li", match: :first)
items = all("#{menu_selector} li")
expect(items.first).to have_content 'Comment'
+ expect(items.first).to have_content "Add a general comment to this #{resource_name}."
expect(items.first).to have_selector '.fa-check'
expect(items.first['class']).to match 'droplab-item-selected'
expect(items.last).to have_content 'Start discussion'
+ expect(items.last).to have_content "Discuss a specific suggestion or question#{' that needs to be resolved' if resource_name == 'merge request'}."
expect(items.last).not_to have_selector '.fa-check'
expect(items.last['class']).not_to match 'droplab-item-selected'
end
- it 'closes the menu when clicking the toggle' do
+ it 'closes the menu when clicking the toggle or body' do
find(toggle_selector).click
expect(page).not_to have_selector menu_selector
- end
- it 'closes the menu when clicking the body' do
+ find(toggle_selector).click
find('body').click
expect(page).not_to have_selector menu_selector
@@ -104,12 +85,10 @@ shared_examples 'discussion comments' do |resource_name|
all("#{menu_selector} li").last.click
end
- it 'updates the note_type input to "DiscussionNote"' do
- expect(find("#{form_selector} #note_type", visible: false).value).to eq('DiscussionNote')
- end
-
- it 'updates the submit button text' do
+ it 'updates the submit button text, note_type input and closes the dropdown' do
expect(find(dropdown_selector)).to have_content 'Start discussion'
+ expect(find("#{form_selector} #note_type", visible: false).value).to eq('DiscussionNote')
+ expect(page).not_to have_selector menu_selector
end
if resource_name =~ /(issue|merge request)/
@@ -124,10 +103,6 @@ shared_examples 'discussion comments' do |resource_name|
end
end
- it 'closes the dropdown' do
- expect(page).not_to have_selector menu_selector
- end
-
it 'clicking "Start discussion" will post a discussion' do
find(submit_selector).click
@@ -176,12 +151,10 @@ shared_examples 'discussion comments' do |resource_name|
find("#{menu_selector} li", match: :first).click
end
- it 'clears the note_type input"' do
- expect(find("#{form_selector} #note_type", visible: false).value).to eq('')
- end
-
- it 'updates the submit button text' do
+ it 'updates the submit button text, clears the note_type input and closes the dropdown' do
expect(find(dropdown_selector)).to have_content 'Comment'
+ expect(find("#{form_selector} #note_type", visible: false).value).to eq('')
+ expect(page).not_to have_selector menu_selector
end
if resource_name =~ /(issue|merge request)/
@@ -196,10 +169,6 @@ shared_examples 'discussion comments' do |resource_name|
end
end
- it 'closes the dropdown' do
- expect(page).not_to have_selector menu_selector
- end
-
it 'should have "Comment" selected when opening the menu' do
find(toggle_selector).click
@@ -242,54 +211,3 @@ shared_examples 'discussion comments' do |resource_name|
end
end
end
-
-describe 'Discussion Comments', :feature, :js do
- include RepoHelpers
-
- let(:user) { create(:user) }
- let(:project) { create(:project) }
-
- before do
- project.team << [user, :developer]
-
- login_as(user)
- end
-
- describe 'on a merge request' do
- let(:merge_request) { create(:merge_request, source_project: project) }
-
- before do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
- end
-
- it_behaves_like 'discussion comments', 'merge request'
- end
-
- describe 'on an issue' do
- let(:issue) { create(:issue, project: project) }
-
- before do
- visit namespace_project_issue_path(project.namespace, project, issue)
- end
-
- it_behaves_like 'discussion comments', 'issue'
- end
-
- describe 'on an snippet' do
- let(:snippet) { create(:project_snippet, :private, project: project, author: user) }
-
- before do
- visit namespace_project_snippet_path(project.namespace, project, snippet)
- end
-
- it_behaves_like 'discussion comments', 'snippet'
- end
-
- describe 'on a commit' do
- before do
- visit namespace_project_commit_path(project.namespace, project, sample_commit.id)
- end
-
- it_behaves_like 'discussion comments', 'commit'
- end
-end
diff --git a/spec/support/fixture_helpers.rb b/spec/support/fixture_helpers.rb
index a05c9d18002..5515c355cea 100644
--- a/spec/support/fixture_helpers.rb
+++ b/spec/support/fixture_helpers.rb
@@ -1,8 +1,11 @@
module FixtureHelpers
def fixture_file(filename)
return '' if filename.blank?
- file_path = File.expand_path(Rails.root.join('spec/fixtures/', filename))
- File.read(file_path)
+ File.read(expand_fixture_path(filename))
+ end
+
+ def expand_fixture_path(filename)
+ File.expand_path(Rails.root.join('spec/fixtures/', filename))
end
end
diff --git a/spec/support/gitaly.rb b/spec/support/gitaly.rb
new file mode 100644
index 00000000000..7aca902fc61
--- /dev/null
+++ b/spec/support/gitaly.rb
@@ -0,0 +1,7 @@
+if Gitlab::GitalyClient.enabled?
+ RSpec.configure do |config|
+ config.before(:each) do
+ allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(true)
+ end
+ end
+end
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 1b5cb71a6b0..60c2096a126 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -64,6 +64,8 @@ module TestEnv
# Setup GitLab shell for test instance
setup_gitlab_shell
+ setup_gitaly if Gitlab::GitalyClient.enabled?
+
# Create repository for FactoryGirl.create(:project)
setup_factory_repo
@@ -71,6 +73,10 @@ module TestEnv
setup_forked_repo
end
+ def cleanup
+ stop_gitaly
+ end
+
def disable_mailer
allow_any_instance_of(NotificationService).to receive(:mailer).
and_return(double.as_null_object)
@@ -92,7 +98,7 @@ module TestEnv
tmp_test_path = Rails.root.join('tmp', 'tests', '**')
Dir[tmp_test_path].each do |entry|
- unless File.basename(entry) =~ /\Agitlab-(shell|test|test_bare|test-fork|test-fork_bare)\z/
+ unless File.basename(entry) =~ /\A(gitaly|gitlab-(shell|test|test_bare|test-fork|test-fork_bare))\z/
FileUtils.rm_rf(entry)
end
end
@@ -110,6 +116,28 @@ module TestEnv
end
end
+ def setup_gitaly
+ socket_path = Gitlab::GitalyClient.get_address('default').sub(/\Aunix:/, '')
+ gitaly_dir = File.dirname(socket_path)
+
+ unless File.directory?(gitaly_dir) || system('rake', "gitlab:gitaly:install[#{gitaly_dir}]")
+ raise "Can't clone gitaly"
+ end
+
+ start_gitaly(gitaly_dir, socket_path)
+ end
+
+ def start_gitaly(gitaly_dir, socket_path)
+ gitaly_exec = File.join(gitaly_dir, 'gitaly')
+ @gitaly_pid = spawn({ "GITALY_SOCKET_PATH" => socket_path }, gitaly_exec, [:out, :err] => '/dev/null')
+ end
+
+ def stop_gitaly
+ return unless @gitaly_pid
+
+ Process.kill('KILL', @gitaly_pid)
+ end
+
def setup_factory_repo
setup_repo(factory_repo_path, factory_repo_path_bare, factory_repo_name,
BRANCH_SHA)
diff --git a/spec/tasks/gitlab/gitaly_rake_spec.rb b/spec/tasks/gitlab/gitaly_rake_spec.rb
index b369dcbb305..aaf998a546f 100644
--- a/spec/tasks/gitlab/gitaly_rake_spec.rb
+++ b/spec/tasks/gitlab/gitaly_rake_spec.rb
@@ -8,7 +8,7 @@ describe 'gitlab:gitaly namespace rake task' do
describe 'install' do
let(:repo) { 'https://gitlab.com/gitlab-org/gitaly.git' }
let(:clone_path) { Rails.root.join('tmp/tests/gitaly').to_s }
- let(:tag) { "v#{File.read(Rails.root.join(Gitlab::GitalyClient::SERVER_VERSION_FILE)).chomp}" }
+ let(:version) { File.read(Rails.root.join(Gitlab::GitalyClient::SERVER_VERSION_FILE)).chomp }
context 'no dir given' do
it 'aborts and display a help message' do
@@ -21,7 +21,7 @@ describe 'gitlab:gitaly namespace rake task' do
context 'when an underlying Git command fail' do
it 'aborts and display a help message' do
expect_any_instance_of(Object).
- to receive(:checkout_or_clone_tag).and_raise 'Git error'
+ to receive(:checkout_or_clone_version).and_raise 'Git error'
expect { run_rake_task('gitlab:gitaly:install', clone_path) }.to raise_error 'Git error'
end
@@ -32,9 +32,9 @@ describe 'gitlab:gitaly namespace rake task' do
expect(Dir).to receive(:chdir).with(clone_path)
end
- it 'calls checkout_or_clone_tag with the right arguments' do
+ it 'calls checkout_or_clone_version with the right arguments' do
expect_any_instance_of(Object).
- to receive(:checkout_or_clone_tag).with(tag: tag, repo: repo, target_dir: clone_path)
+ to receive(:checkout_or_clone_version).with(version: version, repo: repo, target_dir: clone_path)
run_rake_task('gitlab:gitaly:install', clone_path)
end
@@ -48,7 +48,7 @@ describe 'gitlab:gitaly namespace rake task' do
context 'gmake is available' do
before do
- expect_any_instance_of(Object).to receive(:checkout_or_clone_tag)
+ expect_any_instance_of(Object).to receive(:checkout_or_clone_version)
allow_any_instance_of(Object).to receive(:run_command!).with(['gmake']).and_return(true)
end
@@ -62,7 +62,7 @@ describe 'gitlab:gitaly namespace rake task' do
context 'gmake is not available' do
before do
- expect_any_instance_of(Object).to receive(:checkout_or_clone_tag)
+ expect_any_instance_of(Object).to receive(:checkout_or_clone_version)
allow_any_instance_of(Object).to receive(:run_command!).with(['make']).and_return(true)
end
diff --git a/spec/tasks/gitlab/task_helpers_spec.rb b/spec/tasks/gitlab/task_helpers_spec.rb
index 86e42d845ce..3d9ba7cdc6f 100644
--- a/spec/tasks/gitlab/task_helpers_spec.rb
+++ b/spec/tasks/gitlab/task_helpers_spec.rb
@@ -10,19 +10,38 @@ describe Gitlab::TaskHelpers do
let(:repo) { 'https://gitlab.com/gitlab-org/gitlab-test.git' }
let(:clone_path) { Rails.root.join('tmp/tests/task_helpers_tests').to_s }
+ let(:version) { '1.1.0' }
let(:tag) { 'v1.1.0' }
- describe '#checkout_or_clone_tag' do
+ describe '#checkout_or_clone_version' do
before do
allow(subject).to receive(:run_command!)
- expect(subject).to receive(:reset_to_tag).with(tag, clone_path)
end
- context 'target_dir does not exist' do
- it 'clones the repo, retrieve the tag from origin, and checkout the tag' do
+ it 'checkout the version and reset to it' do
+ expect(subject).to receive(:checkout_version).with(tag, clone_path)
+ expect(subject).to receive(:reset_to_version).with(tag, clone_path)
+
+ subject.checkout_or_clone_version(version: version, repo: repo, target_dir: clone_path)
+ end
+
+ context 'with a branch version' do
+ let(:version) { '=branch_name' }
+ let(:branch) { 'branch_name' }
+
+ it 'checkout the version and reset to it with a branch name' do
+ expect(subject).to receive(:checkout_version).with(branch, clone_path)
+ expect(subject).to receive(:reset_to_version).with(branch, clone_path)
+
+ subject.checkout_or_clone_version(version: version, repo: repo, target_dir: clone_path)
+ end
+ end
+
+ context "target_dir doesn't exist" do
+ it 'clones the repo' do
expect(subject).to receive(:clone_repo).with(repo, clone_path)
- subject.checkout_or_clone_tag(tag: tag, repo: repo, target_dir: clone_path)
+ subject.checkout_or_clone_version(version: version, repo: repo, target_dir: clone_path)
end
end
@@ -31,10 +50,10 @@ describe Gitlab::TaskHelpers do
expect(Dir).to receive(:exist?).and_return(true)
end
- it 'fetch and checkout the tag' do
- expect(subject).to receive(:checkout_tag).with(tag, clone_path)
+ it "doesn't clone the repository" do
+ expect(subject).not_to receive(:clone_repo)
- subject.checkout_or_clone_tag(tag: tag, repo: repo, target_dir: clone_path)
+ subject.checkout_or_clone_version(version: version, repo: repo, target_dir: clone_path)
end
end
end
@@ -48,49 +67,23 @@ describe Gitlab::TaskHelpers do
end
end
- describe '#checkout_tag' do
+ describe '#checkout_version' do
it 'clones the repo in the target dir' do
expect(subject).
- to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} fetch --tags --quiet])
+ to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} fetch --quiet])
expect(subject).
to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} checkout --quiet #{tag}])
- subject.checkout_tag(tag, clone_path)
+ subject.checkout_version(tag, clone_path)
end
end
- describe '#reset_to_tag' do
- let(:tag) { 'v1.1.0' }
- before do
+ describe '#reset_to_version' do
+ it 'resets --hard to the given version' do
expect(subject).
to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} reset --hard #{tag}])
- end
- context 'when the tag is not checked out locally' do
- before do
- expect(subject).
- to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} describe -- #{tag}]).and_raise(Gitlab::TaskFailedError)
- end
-
- it 'fetch origin, ensure the tag exists, and resets --hard to the given tag' do
- expect(subject).
- to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} fetch origin])
- expect(subject).
- to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} describe -- origin/#{tag}]).and_return(tag)
-
- subject.reset_to_tag(tag, clone_path)
- end
- end
-
- context 'when the tag is checked out locally' do
- before do
- expect(subject).
- to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} describe -- #{tag}]).and_return(tag)
- end
-
- it 'resets --hard to the given tag' do
- subject.reset_to_tag(tag, clone_path)
- end
+ subject.reset_to_version(tag, clone_path)
end
end
end
diff --git a/spec/tasks/gitlab/workhorse_rake_spec.rb b/spec/tasks/gitlab/workhorse_rake_spec.rb
index 8a66a4aa047..63d1cf2bbe5 100644
--- a/spec/tasks/gitlab/workhorse_rake_spec.rb
+++ b/spec/tasks/gitlab/workhorse_rake_spec.rb
@@ -8,7 +8,7 @@ describe 'gitlab:workhorse namespace rake task' do
describe 'install' do
let(:repo) { 'https://gitlab.com/gitlab-org/gitlab-workhorse.git' }
let(:clone_path) { Rails.root.join('tmp/tests/gitlab-workhorse').to_s }
- let(:tag) { "v#{File.read(Rails.root.join(Gitlab::Workhorse::VERSION_FILE)).chomp}" }
+ let(:version) { File.read(Rails.root.join(Gitlab::Workhorse::VERSION_FILE)).chomp }
context 'no dir given' do
it 'aborts and display a help message' do
@@ -21,7 +21,7 @@ describe 'gitlab:workhorse namespace rake task' do
context 'when an underlying Git command fail' do
it 'aborts and display a help message' do
expect_any_instance_of(Object).
- to receive(:checkout_or_clone_tag).and_raise 'Git error'
+ to receive(:checkout_or_clone_version).and_raise 'Git error'
expect { run_rake_task('gitlab:workhorse:install', clone_path) }.to raise_error 'Git error'
end
@@ -32,9 +32,9 @@ describe 'gitlab:workhorse namespace rake task' do
expect(Dir).to receive(:chdir).with(clone_path)
end
- it 'calls checkout_or_clone_tag with the right arguments' do
+ it 'calls checkout_or_clone_version with the right arguments' do
expect_any_instance_of(Object).
- to receive(:checkout_or_clone_tag).with(tag: tag, repo: repo, target_dir: clone_path)
+ to receive(:checkout_or_clone_version).with(version: version, repo: repo, target_dir: clone_path)
run_rake_task('gitlab:workhorse:install', clone_path)
end
@@ -48,7 +48,7 @@ describe 'gitlab:workhorse namespace rake task' do
context 'gmake is available' do
before do
- expect_any_instance_of(Object).to receive(:checkout_or_clone_tag)
+ expect_any_instance_of(Object).to receive(:checkout_or_clone_version)
allow_any_instance_of(Object).to receive(:run_command!).with(['gmake']).and_return(true)
end
@@ -62,7 +62,7 @@ describe 'gitlab:workhorse namespace rake task' do
context 'gmake is not available' do
before do
- expect_any_instance_of(Object).to receive(:checkout_or_clone_tag)
+ expect_any_instance_of(Object).to receive(:checkout_or_clone_version)
allow_any_instance_of(Object).to receive(:run_command!).with(['make']).and_return(true)
end
diff --git a/spec/views/projects/registry/repositories/index.html.haml_spec.rb b/spec/views/projects/registry/repositories/index.html.haml_spec.rb
new file mode 100644
index 00000000000..ceeace3dc8d
--- /dev/null
+++ b/spec/views/projects/registry/repositories/index.html.haml_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+describe 'projects/registry/repositories/index', :view do
+ let(:group) { create(:group, path: 'group') }
+ let(:project) { create(:empty_project, group: group, path: 'test') }
+
+ let(:repository) do
+ create(:container_repository, project: project, name: 'image')
+ end
+
+ before do
+ stub_container_registry_config(enabled: true,
+ host_port: 'registry.gitlab',
+ api_url: 'http://registry.gitlab')
+
+ stub_container_registry_tags(repository: :any, tags: [:latest])
+
+ assign(:project, project)
+ assign(:images, [repository])
+
+ allow(view).to receive(:can?).and_return(true)
+ end
+
+ it 'contains container repository path' do
+ render
+
+ expect(rendered).to have_content 'group/test/image'
+ end
+
+ it 'contains attribute for copying tag location into clipboard' do
+ render
+
+ expect(rendered).to have_css 'button[data-clipboard-text="docker pull ' \
+ 'registry.gitlab/group/test/image:latest"]'
+ end
+end
diff --git a/vendor/assets/javascripts/notebooklab.js b/vendor/assets/javascripts/notebooklab.js
index 296271205d1..b8cfdc53b48 100644
--- a/vendor/assets/javascripts/notebooklab.js
+++ b/vendor/assets/javascripts/notebooklab.js
@@ -233,22 +233,6 @@ var Component = __webpack_require__(0)(
/* cssModules */
null
)
-Component.options.__file = "/Users/phil/Projects/notebooklab/src/cells/prompt.vue"
-if (Component.esModule && Object.keys(Component.esModule).some(function (key) {return key !== "default" && key !== "__esModule"})) {console.error("named exports are not supported in *.vue files.")}
-if (Component.options.functional) {console.error("[vue-loader] prompt.vue: functional components are not supported with templates, they should use render functions.")}
-
-/* hot reload */
-if (false) {(function () {
- var hotAPI = require("vue-hot-reload-api")
- hotAPI.install(require("vue"), false)
- if (!hotAPI.compatible) return
- module.hot.accept()
- if (!module.hot.data) {
- hotAPI.createRecord("data-v-4f6bf458", Component.options)
- } else {
- hotAPI.reload("data-v-4f6bf458", Component.options)
- }
-})()}
module.exports = Component.exports
@@ -515,22 +499,6 @@ var Component = __webpack_require__(0)(
/* cssModules */
null
)
-Component.options.__file = "/Users/phil/Projects/notebooklab/src/cells/code/index.vue"
-if (Component.esModule && Object.keys(Component.esModule).some(function (key) {return key !== "default" && key !== "__esModule"})) {console.error("named exports are not supported in *.vue files.")}
-if (Component.options.functional) {console.error("[vue-loader] index.vue: functional components are not supported with templates, they should use render functions.")}
-
-/* hot reload */
-if (false) {(function () {
- var hotAPI = require("vue-hot-reload-api")
- hotAPI.install(require("vue"), false)
- if (!hotAPI.compatible) return
- module.hot.accept()
- if (!module.hot.data) {
- hotAPI.createRecord("data-v-d42105b8", Component.options)
- } else {
- hotAPI.reload("data-v-d42105b8", Component.options)
- }
-})()}
module.exports = Component.exports
@@ -553,22 +521,6 @@ var Component = __webpack_require__(0)(
/* cssModules */
null
)
-Component.options.__file = "/Users/phil/Projects/notebooklab/src/index.vue"
-if (Component.esModule && Object.keys(Component.esModule).some(function (key) {return key !== "default" && key !== "__esModule"})) {console.error("named exports are not supported in *.vue files.")}
-if (Component.options.functional) {console.error("[vue-loader] index.vue: functional components are not supported with templates, they should use render functions.")}
-
-/* hot reload */
-if (false) {(function () {
- var hotAPI = require("vue-hot-reload-api")
- hotAPI.install(require("vue"), false)
- if (!hotAPI.compatible) return
- module.hot.accept()
- if (!module.hot.data) {
- hotAPI.createRecord("data-v-4cb2b168", Component.options)
- } else {
- hotAPI.reload("data-v-4cb2b168", Component.options)
- }
-})()}
module.exports = Component.exports
@@ -630,9 +582,9 @@ exports.default = {
rawInputCode: function rawInputCode() {
if (this.cell.source) {
return this.cell.source.join('');
- } else {
- return '';
}
+
+ return '';
},
hasOutput: function hasOutput() {
return this.cell.outputs.length;
@@ -747,6 +699,48 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
//
//
+var renderer = new _marked2.default.Renderer();
+
+/*
+ Regex to match KaTex blocks.
+
+ Supports the following:
+
+ \begin{equation}<math>\end{equation}
+ $$<math>$$
+ inline $<math>$
+
+ The matched text then goes through the KaTex renderer & then outputs the HTML
+*/
+var katexRegexString = '(\n ^\\\\begin{[a-zA-Z]+}\\s\n |\n ^\\$\\$\n |\n \\s\\$(?!\\$)\n)\n (.+?)\n(\n \\s\\\\end{[a-zA-Z]+}$\n |\n \\$\\$$\n |\n \\$\n)\n'.replace(/\s/g, '').trim();
+
+renderer.paragraph = function (t) {
+ var text = t;
+ var inline = false;
+
+ if (typeof katex !== 'undefined') {
+ var katexString = text.replace(/\\/g, '\\');
+ var matches = new RegExp(katexRegexString, 'gi').exec(katexString);
+
+ if (matches && matches.length > 0) {
+ if (matches[1].trim() === '$' && matches[3].trim() === '$') {
+ inline = true;
+
+ text = katexString.replace(matches[0], '') + ' ' + katex.renderToString(matches[2]);
+ } else {
+ text = katex.renderToString(matches[2]);
+ }
+ }
+ }
+
+ return '<p class="' + (inline ? 'inline-katex' : '') + '">' + text + '</p>';
+};
+
+_marked2.default.setOptions({
+ sanitize: true,
+ renderer: renderer
+});
+
exports.default = {
components: {
prompt: _prompt2.default
@@ -759,20 +753,7 @@ exports.default = {
},
computed: {
markdown: function markdown() {
- var regex = new RegExp('^\\$\\$(.*)\\$\\$$', 'g');
-
- var source = this.cell.source.map(function (line) {
- var matches = regex.exec(line.trim());
-
- // Only render use the Katex library if it is actually loaded
- if (matches && matches.length > 0 && typeof katex !== 'undefined') {
- return katex.renderToString(matches[1]);
- }
-
- return line;
- });
-
- return (0, _marked2.default)(source.join(''));
+ return (0, _marked2.default)(this.cell.source.join(''));
}
}
};
@@ -1030,13 +1011,14 @@ exports.default = {
cells: []
};
- return this.notebook.worksheets.reduce(function (data, sheet) {
- data.cells = data.cells.concat(sheet.cells);
- return data;
+ return this.notebook.worksheets.reduce(function (cellData, sheet) {
+ var cellDataCopy = cellData;
+ cellDataCopy.cells = cellDataCopy.cells.concat(sheet.cells);
+ return cellDataCopy;
}, data).cells;
- } else {
- return this.notebook.cells;
}
+
+ return this.notebook.cells;
},
hasNotebook: function hasNotebook() {
return Object.keys(this.notebook).length;
@@ -3052,7 +3034,7 @@ exports = module.exports = __webpack_require__(1)(undefined);
// module
-exports.push([module.i, "\n.cell[data-v-3ac4c361] {\n flex-direction: column;\n}\n", ""]);
+exports.push([module.i, ".cell[data-v-3ac4c361]{flex-direction:column}", ""]);
// exports
@@ -3066,7 +3048,7 @@ exports = module.exports = __webpack_require__(1)(undefined);
// module
-exports.push([module.i, "\n.cell,\n.input,\n.output {\n display: flex;\n width: 100%;\n margin-bottom: 10px;\n}\n.cell pre {\n margin: 0;\n width: 100%;\n}\n", ""]);
+exports.push([module.i, ".cell,.input,.output{display:flex;width:100%;margin-bottom:10px}.cell pre{margin:0;width:100%}", ""]);
// exports
@@ -3080,7 +3062,7 @@ exports = module.exports = __webpack_require__(1)(undefined);
// module
-exports.push([module.i, "\n.prompt[data-v-4f6bf458] {\n padding: 0 10px;\n min-width: 7em;\n font-family: monospace;\n}\n", ""]);
+exports.push([module.i, ".prompt[data-v-4f6bf458]{padding:0 10px;min-width:7em;font-family:monospace}", ""]);
// exports
@@ -3094,7 +3076,7 @@ exports = module.exports = __webpack_require__(1)(undefined);
// module
-exports.push([module.i, "\n.markdown .katex {\n display: block;\n text-align: center;\n}\n", ""]);
+exports.push([module.i, ".markdown .katex{display:block;text-align:center}.markdown .inline-katex .katex{display:inline;text-align:initial}", ""]);
// exports
@@ -5382,22 +5364,6 @@ var Component = __webpack_require__(0)(
/* cssModules */
null
)
-Component.options.__file = "/Users/phil/Projects/notebooklab/src/cells/code.vue"
-if (Component.esModule && Object.keys(Component.esModule).some(function (key) {return key !== "default" && key !== "__esModule"})) {console.error("named exports are not supported in *.vue files.")}
-if (Component.options.functional) {console.error("[vue-loader] code.vue: functional components are not supported with templates, they should use render functions.")}
-
-/* hot reload */
-if (false) {(function () {
- var hotAPI = require("vue-hot-reload-api")
- hotAPI.install(require("vue"), false)
- if (!hotAPI.compatible) return
- module.hot.accept()
- if (!module.hot.data) {
- hotAPI.createRecord("data-v-3ac4c361", Component.options)
- } else {
- hotAPI.reload("data-v-3ac4c361", Component.options)
- }
-})()}
module.exports = Component.exports
@@ -5420,22 +5386,6 @@ var Component = __webpack_require__(0)(
/* cssModules */
null
)
-Component.options.__file = "/Users/phil/Projects/notebooklab/src/cells/markdown.vue"
-if (Component.esModule && Object.keys(Component.esModule).some(function (key) {return key !== "default" && key !== "__esModule"})) {console.error("named exports are not supported in *.vue files.")}
-if (Component.options.functional) {console.error("[vue-loader] markdown.vue: functional components are not supported with templates, they should use render functions.")}
-
-/* hot reload */
-if (false) {(function () {
- var hotAPI = require("vue-hot-reload-api")
- hotAPI.install(require("vue"), false)
- if (!hotAPI.compatible) return
- module.hot.accept()
- if (!module.hot.data) {
- hotAPI.createRecord("data-v-7342b363", Component.options)
- } else {
- hotAPI.reload("data-v-7342b363", Component.options)
- }
-})()}
module.exports = Component.exports
@@ -5454,22 +5404,6 @@ var Component = __webpack_require__(0)(
/* cssModules */
null
)
-Component.options.__file = "/Users/phil/Projects/notebooklab/src/cells/output/html.vue"
-if (Component.esModule && Object.keys(Component.esModule).some(function (key) {return key !== "default" && key !== "__esModule"})) {console.error("named exports are not supported in *.vue files.")}
-if (Component.options.functional) {console.error("[vue-loader] html.vue: functional components are not supported with templates, they should use render functions.")}
-
-/* hot reload */
-if (false) {(function () {
- var hotAPI = require("vue-hot-reload-api")
- hotAPI.install(require("vue"), false)
- if (!hotAPI.compatible) return
- module.hot.accept()
- if (!module.hot.data) {
- hotAPI.createRecord("data-v-48ada535", Component.options)
- } else {
- hotAPI.reload("data-v-48ada535", Component.options)
- }
-})()}
module.exports = Component.exports
@@ -5488,22 +5422,6 @@ var Component = __webpack_require__(0)(
/* cssModules */
null
)
-Component.options.__file = "/Users/phil/Projects/notebooklab/src/cells/output/image.vue"
-if (Component.esModule && Object.keys(Component.esModule).some(function (key) {return key !== "default" && key !== "__esModule"})) {console.error("named exports are not supported in *.vue files.")}
-if (Component.options.functional) {console.error("[vue-loader] image.vue: functional components are not supported with templates, they should use render functions.")}
-
-/* hot reload */
-if (false) {(function () {
- var hotAPI = require("vue-hot-reload-api")
- hotAPI.install(require("vue"), false)
- if (!hotAPI.compatible) return
- module.hot.accept()
- if (!module.hot.data) {
- hotAPI.createRecord("data-v-09b68c41", Component.options)
- } else {
- hotAPI.reload("data-v-09b68c41", Component.options)
- }
-})()}
module.exports = Component.exports
@@ -5522,29 +5440,13 @@ var Component = __webpack_require__(0)(
/* cssModules */
null
)
-Component.options.__file = "/Users/phil/Projects/notebooklab/src/cells/output/index.vue"
-if (Component.esModule && Object.keys(Component.esModule).some(function (key) {return key !== "default" && key !== "__esModule"})) {console.error("named exports are not supported in *.vue files.")}
-if (Component.options.functional) {console.error("[vue-loader] index.vue: functional components are not supported with templates, they should use render functions.")}
-
-/* hot reload */
-if (false) {(function () {
- var hotAPI = require("vue-hot-reload-api")
- hotAPI.install(require("vue"), false)
- if (!hotAPI.compatible) return
- module.hot.accept()
- if (!module.hot.data) {
- hotAPI.createRecord("data-v-0dec7838", Component.options)
- } else {
- hotAPI.reload("data-v-0dec7838", Component.options)
- }
-})()}
module.exports = Component.exports
/***/ }),
/* 34 */
-/***/ (function(module, exports, __webpack_require__) {
+/***/ (function(module, exports) {
module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;
return _c('div', {
@@ -5555,17 +5457,10 @@ module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c
}
})], 1)
},staticRenderFns: []}
-module.exports.render._withStripped = true
-if (false) {
- module.hot.accept()
- if (module.hot.data) {
- require("vue-hot-reload-api").rerender("data-v-09b68c41", module.exports)
- }
-}
/***/ }),
/* 35 */
-/***/ (function(module, exports, __webpack_require__) {
+/***/ (function(module, exports) {
module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;
return _c(_vm.componentName, {
@@ -5579,17 +5474,10 @@ module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c
}
})
},staticRenderFns: []}
-module.exports.render._withStripped = true
-if (false) {
- module.hot.accept()
- if (module.hot.data) {
- require("vue-hot-reload-api").rerender("data-v-0dec7838", module.exports)
- }
-}
/***/ }),
/* 36 */
-/***/ (function(module, exports, __webpack_require__) {
+/***/ (function(module, exports) {
module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;
return _c('div', {
@@ -5609,17 +5497,10 @@ module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c
}
}) : _vm._e()], 1)
},staticRenderFns: []}
-module.exports.render._withStripped = true
-if (false) {
- module.hot.accept()
- if (module.hot.data) {
- require("vue-hot-reload-api").rerender("data-v-3ac4c361", module.exports)
- }
-}
/***/ }),
/* 37 */
-/***/ (function(module, exports, __webpack_require__) {
+/***/ (function(module, exports) {
module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;
return _c('div', {
@@ -5630,17 +5511,10 @@ module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c
}
})], 1)
},staticRenderFns: []}
-module.exports.render._withStripped = true
-if (false) {
- module.hot.accept()
- if (module.hot.data) {
- require("vue-hot-reload-api").rerender("data-v-48ada535", module.exports)
- }
-}
/***/ }),
/* 38 */
-/***/ (function(module, exports, __webpack_require__) {
+/***/ (function(module, exports) {
module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;
return (_vm.hasNotebook) ? _c('div', _vm._l((_vm.cells), function(cell, index) {
@@ -5654,34 +5528,20 @@ module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c
})
})) : _vm._e()
},staticRenderFns: []}
-module.exports.render._withStripped = true
-if (false) {
- module.hot.accept()
- if (module.hot.data) {
- require("vue-hot-reload-api").rerender("data-v-4cb2b168", module.exports)
- }
-}
/***/ }),
/* 39 */
-/***/ (function(module, exports, __webpack_require__) {
+/***/ (function(module, exports) {
module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;
return _c('div', {
staticClass: "prompt"
}, [(_vm.type && _vm.count) ? _c('span', [_vm._v("\n " + _vm._s(_vm.type) + " [" + _vm._s(_vm.count) + "]:\n ")]) : _vm._e()])
},staticRenderFns: []}
-module.exports.render._withStripped = true
-if (false) {
- module.hot.accept()
- if (module.hot.data) {
- require("vue-hot-reload-api").rerender("data-v-4f6bf458", module.exports)
- }
-}
/***/ }),
/* 40 */
-/***/ (function(module, exports, __webpack_require__) {
+/***/ (function(module, exports) {
module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;
return _c('div', {
@@ -5693,17 +5553,10 @@ module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c
}
})], 1)
},staticRenderFns: []}
-module.exports.render._withStripped = true
-if (false) {
- module.hot.accept()
- if (module.hot.data) {
- require("vue-hot-reload-api").rerender("data-v-7342b363", module.exports)
- }
-}
/***/ }),
/* 41 */
-/***/ (function(module, exports, __webpack_require__) {
+/***/ (function(module, exports) {
module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;
return _c('div', {
@@ -5722,13 +5575,6 @@ module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c
}
}, [_vm._v("\n ")])], 1)
},staticRenderFns: []}
-module.exports.render._withStripped = true
-if (false) {
- module.hot.accept()
- if (module.hot.data) {
- require("vue-hot-reload-api").rerender("data-v-d42105b8", module.exports)
- }
-}
/***/ }),
/* 42 */
@@ -5741,13 +5587,13 @@ var content = __webpack_require__(19);
if(typeof content === 'string') content = [[module.i, content, '']];
if(content.locals) module.exports = content.locals;
// add the styles to the DOM
-var update = __webpack_require__(3)("06fc6a9f", content, false);
+var update = __webpack_require__(3)("74a276de", content, true);
// Hot Module Replacement
if(false) {
// When the styles change, update the <style> tags
if(!content.locals) {
- module.hot.accept("!!../../node_modules/css-loader/index.js!../../node_modules/vue-loader/lib/style-compiler/index.js?{\"id\":\"data-v-3ac4c361\",\"scoped\":true,\"hasInlineConfig\":false}!../../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./code.vue", function() {
- var newContent = require("!!../../node_modules/css-loader/index.js!../../node_modules/vue-loader/lib/style-compiler/index.js?{\"id\":\"data-v-3ac4c361\",\"scoped\":true,\"hasInlineConfig\":false}!../../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./code.vue");
+ module.hot.accept("!!../../node_modules/css-loader/index.js?minimize!../../node_modules/vue-loader/lib/style-compiler/index.js?{\"id\":\"data-v-3ac4c361\",\"scoped\":true,\"hasInlineConfig\":false}!../../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./code.vue", function() {
+ var newContent = require("!!../../node_modules/css-loader/index.js?minimize!../../node_modules/vue-loader/lib/style-compiler/index.js?{\"id\":\"data-v-3ac4c361\",\"scoped\":true,\"hasInlineConfig\":false}!../../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./code.vue");
if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];
update(newContent);
});
@@ -5767,13 +5613,13 @@ var content = __webpack_require__(20);
if(typeof content === 'string') content = [[module.i, content, '']];
if(content.locals) module.exports = content.locals;
// add the styles to the DOM
-var update = __webpack_require__(3)("87c28124", content, false);
+var update = __webpack_require__(3)("55f9d67b", content, true);
// Hot Module Replacement
if(false) {
// When the styles change, update the <style> tags
if(!content.locals) {
- module.hot.accept("!!../node_modules/css-loader/index.js!../node_modules/vue-loader/lib/style-compiler/index.js?{\"id\":\"data-v-4cb2b168\",\"scoped\":false,\"hasInlineConfig\":false}!../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./index.vue", function() {
- var newContent = require("!!../node_modules/css-loader/index.js!../node_modules/vue-loader/lib/style-compiler/index.js?{\"id\":\"data-v-4cb2b168\",\"scoped\":false,\"hasInlineConfig\":false}!../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./index.vue");
+ module.hot.accept("!!../node_modules/css-loader/index.js?minimize!../node_modules/vue-loader/lib/style-compiler/index.js?{\"id\":\"data-v-4cb2b168\",\"scoped\":false,\"hasInlineConfig\":false}!../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./index.vue", function() {
+ var newContent = require("!!../node_modules/css-loader/index.js?minimize!../node_modules/vue-loader/lib/style-compiler/index.js?{\"id\":\"data-v-4cb2b168\",\"scoped\":false,\"hasInlineConfig\":false}!../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./index.vue");
if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];
update(newContent);
});
@@ -5793,13 +5639,13 @@ var content = __webpack_require__(21);
if(typeof content === 'string') content = [[module.i, content, '']];
if(content.locals) module.exports = content.locals;
// add the styles to the DOM
-var update = __webpack_require__(3)("5b60b003", content, false);
+var update = __webpack_require__(3)("1096aefc", content, true);
// Hot Module Replacement
if(false) {
// When the styles change, update the <style> tags
if(!content.locals) {
- module.hot.accept("!!../../node_modules/css-loader/index.js!../../node_modules/vue-loader/lib/style-compiler/index.js?{\"id\":\"data-v-4f6bf458\",\"scoped\":true,\"hasInlineConfig\":false}!../../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./prompt.vue", function() {
- var newContent = require("!!../../node_modules/css-loader/index.js!../../node_modules/vue-loader/lib/style-compiler/index.js?{\"id\":\"data-v-4f6bf458\",\"scoped\":true,\"hasInlineConfig\":false}!../../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./prompt.vue");
+ module.hot.accept("!!../../node_modules/css-loader/index.js?minimize!../../node_modules/vue-loader/lib/style-compiler/index.js?{\"id\":\"data-v-4f6bf458\",\"scoped\":true,\"hasInlineConfig\":false}!../../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./prompt.vue", function() {
+ var newContent = require("!!../../node_modules/css-loader/index.js?minimize!../../node_modules/vue-loader/lib/style-compiler/index.js?{\"id\":\"data-v-4f6bf458\",\"scoped\":true,\"hasInlineConfig\":false}!../../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./prompt.vue");
if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];
update(newContent);
});
@@ -5819,13 +5665,13 @@ var content = __webpack_require__(22);
if(typeof content === 'string') content = [[module.i, content, '']];
if(content.locals) module.exports = content.locals;
// add the styles to the DOM
-var update = __webpack_require__(3)("48dda57c", content, false);
+var update = __webpack_require__(3)("58a0689d", content, true);
// Hot Module Replacement
if(false) {
// When the styles change, update the <style> tags
if(!content.locals) {
- module.hot.accept("!!../../node_modules/css-loader/index.js!../../node_modules/vue-loader/lib/style-compiler/index.js?{\"id\":\"data-v-7342b363\",\"scoped\":false,\"hasInlineConfig\":false}!../../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./markdown.vue", function() {
- var newContent = require("!!../../node_modules/css-loader/index.js!../../node_modules/vue-loader/lib/style-compiler/index.js?{\"id\":\"data-v-7342b363\",\"scoped\":false,\"hasInlineConfig\":false}!../../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./markdown.vue");
+ module.hot.accept("!!../../node_modules/css-loader/index.js?minimize!../../node_modules/vue-loader/lib/style-compiler/index.js?{\"id\":\"data-v-7342b363\",\"scoped\":false,\"hasInlineConfig\":false}!../../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./markdown.vue", function() {
+ var newContent = require("!!../../node_modules/css-loader/index.js?minimize!../../node_modules/vue-loader/lib/style-compiler/index.js?{\"id\":\"data-v-7342b363\",\"scoped\":false,\"hasInlineConfig\":false}!../../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./markdown.vue");
if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];
update(newContent);
});
diff --git a/vendor/assets/javascripts/pdf.worker.js b/vendor/assets/javascripts/pdf.worker.js
index f8a94e207f8..970caaaba86 100644
--- a/vendor/assets/javascripts/pdf.worker.js
+++ b/vendor/assets/javascripts/pdf.worker.js
@@ -73,7 +73,7 @@ return /******/ (function(modules) { // webpackBootstrap
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
-/******/ return __webpack_require__(__webpack_require__.s = 16);
+/******/ return __webpack_require__(__webpack_require__.s = 24);
/******/ })
/************************************************************************/
/******/ ({
@@ -20214,6 +20214,7 @@ var stringToUTF8String = sharedUtil.stringToUTF8String;
var warn = sharedUtil.warn;
var createValidAbsoluteUrl = sharedUtil.createValidAbsoluteUrl;
var Util = sharedUtil.Util;
+var Dict = corePrimitives.Dict;
var Ref = corePrimitives.Ref;
var RefSet = corePrimitives.RefSet;
var RefSetCache = corePrimitives.RefSetCache;
@@ -20233,9 +20234,10 @@ var Catalog = function CatalogClosure() {
this.pdfManager = pdfManager;
this.xref = xref;
this.catDict = xref.getCatalogObj();
+ assert(isDict(this.catDict), 'catalog object is not a dictionary');
this.fontCache = new RefSetCache();
this.builtInCMapCache = Object.create(null);
- assert(isDict(this.catDict), 'catalog object is not a dictionary');
+ this.pageKidsCountCache = new RefSetCache();
this.pageFactory = pageFactory;
this.pagePromises = [];
}
@@ -20551,6 +20553,7 @@ var Catalog = function CatalogClosure() {
return shadow(this, 'javaScript', javaScript);
},
cleanup: function Catalog_cleanup() {
+ this.pageKidsCountCache.clear();
var promises = [];
this.fontCache.forEach(function (promise) {
promises.push(promise);
@@ -20577,15 +20580,25 @@ var Catalog = function CatalogClosure() {
getPageDict: function Catalog_getPageDict(pageIndex) {
var capability = createPromiseCapability();
var nodesToVisit = [this.catDict.getRaw('Pages')];
- var currentPageIndex = 0;
- var xref = this.xref;
+ var count,
+ currentPageIndex = 0;
+ var xref = this.xref,
+ pageKidsCountCache = this.pageKidsCountCache;
function next() {
while (nodesToVisit.length) {
var currentNode = nodesToVisit.pop();
if (isRef(currentNode)) {
+ count = pageKidsCountCache.get(currentNode);
+ if (count > 0 && currentPageIndex + count < pageIndex) {
+ currentPageIndex += count;
+ continue;
+ }
xref.fetchAsync(currentNode).then(function (obj) {
if (isDict(obj, 'Page') || isDict(obj) && !obj.has('Kids')) {
if (pageIndex === currentPageIndex) {
+ if (currentNode && !pageKidsCountCache.has(currentNode)) {
+ pageKidsCountCache.put(currentNode, 1);
+ }
capability.resolve([obj, currentNode]);
} else {
currentPageIndex++;
@@ -20599,7 +20612,11 @@ var Catalog = function CatalogClosure() {
return;
}
assert(isDict(currentNode), 'page dictionary kid reference points to wrong type of object');
- var count = currentNode.get('Count');
+ count = currentNode.get('Count');
+ var objId = currentNode.objId;
+ if (objId && !pageKidsCountCache.has(objId)) {
+ pageKidsCountCache.put(objId, count);
+ }
if (currentPageIndex + count <= pageIndex) {
currentPageIndex += count;
continue;
@@ -21191,7 +21208,7 @@ var XRef = function XRefClosure() {
var num = ref.num;
if (num in this.cache) {
var cacheEntry = this.cache[num];
- if (isDict(cacheEntry) && !cacheEntry.objId) {
+ if (cacheEntry instanceof Dict && !cacheEntry.objId) {
cacheEntry.objId = ref.toString();
}
return cacheEntry;
@@ -26178,7 +26195,7 @@ var CMapFactory = function CMapFactoryClosure() {
return Promise.resolve(new IdentityCMap(true, 2));
}
if (BUILT_IN_CMAPS.indexOf(name) === -1) {
- return Promise.reject(new Error('Unknown cMap name: ' + name));
+ return Promise.reject(new Error('Unknown CMap name: ' + name));
}
assert(fetchBuiltInCMap, 'Built-in CMap parameters are not provided.');
return fetchBuiltInCMap(name).then(function (data) {
@@ -28458,9 +28475,6 @@ var Font = function FontClosure() {
}
glyphId = offsetIndex < 0 ? j : offsets[offsetIndex + j - start];
glyphId = glyphId + delta & 0xFFFF;
- if (glyphId === 0) {
- continue;
- }
mappings.push({
charCode: j,
glyphId: glyphId
@@ -37160,8 +37174,8 @@ exports.Type1Parser = Type1Parser;
"use strict";
-var pdfjsVersion = '1.7.395';
-var pdfjsBuild = '07f7c97b';
+var pdfjsVersion = '1.8.172';
+var pdfjsBuild = '8ff1fbe7';
var pdfjsCoreWorker = __w_pdfjs_require__(8);
{
__w_pdfjs_require__(19);
@@ -37646,20 +37660,28 @@ if (typeof PDFJS === 'undefined' || !PDFJS.compatibilityChecked) {
}
})();
(function checkRequestAnimationFrame() {
- function fakeRequestAnimationFrame(callback) {
- window.setTimeout(callback, 20);
+ function installFakeAnimationFrameFunctions() {
+ window.requestAnimationFrame = function (callback) {
+ return window.setTimeout(callback, 20);
+ };
+ window.cancelAnimationFrame = function (timeoutID) {
+ window.clearTimeout(timeoutID);
+ };
}
if (!hasDOM) {
return;
}
if (isIOS) {
- window.requestAnimationFrame = fakeRequestAnimationFrame;
+ installFakeAnimationFrameFunctions();
return;
}
if ('requestAnimationFrame' in window) {
return;
}
- window.requestAnimationFrame = window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || fakeRequestAnimationFrame;
+ window.requestAnimationFrame = window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame;
+ if (!('requestAnimationFrame' in window)) {
+ installFakeAnimationFrameFunctions();
+ }
})();
(function checkCanvasSizeLimitation() {
if (isIOS || isAndroid) {
@@ -38588,7 +38610,7 @@ if (typeof PDFJS === 'undefined' || !PDFJS.compatibilityChecked) {
/***/ }),
-/***/ 16:
+/***/ 24:
/***/ (function(module, exports, __webpack_require__) {
/* Copyright 2016 Mozilla Foundation
diff --git a/vendor/assets/javascripts/pdflab.js b/vendor/assets/javascripts/pdflab.js
index 94e7c40e75e..5d9c348ce35 100644
--- a/vendor/assets/javascripts/pdflab.js
+++ b/vendor/assets/javascripts/pdflab.js
@@ -71,17 +71,10 @@ return /******/ (function(modules) { // webpackBootstrap
/******/ if(installedChunks[chunkId] === 0)
/******/ return Promise.resolve();
/******/
-/******/ // a Promise means "currently loading".
+/******/ // an Promise means "currently loading".
/******/ if(installedChunks[chunkId]) {
/******/ return installedChunks[chunkId][2];
/******/ }
-/******/
-/******/ // setup Promise in chunk cache
-/******/ var promise = new Promise(function(resolve, reject) {
-/******/ installedChunks[chunkId] = [resolve, reject];
-/******/ });
-/******/ installedChunks[chunkId][2] = promise;
-/******/
/******/ // start chunk loading
/******/ var head = document.getElementsByTagName('head')[0];
/******/ var script = document.createElement('script');
@@ -106,8 +99,13 @@ return /******/ (function(modules) { // webpackBootstrap
/******/ installedChunks[chunkId] = undefined;
/******/ }
/******/ };
-/******/ head.appendChild(script);
/******/
+/******/ var promise = new Promise(function(resolve, reject) {
+/******/ installedChunks[chunkId] = [resolve, reject];
+/******/ });
+/******/ installedChunks[chunkId][2] = promise;
+/******/
+/******/ head.appendChild(script);
/******/ return promise;
/******/ };
/******/
@@ -150,7 +148,7 @@ return /******/ (function(modules) { // webpackBootstrap
/******/ __webpack_require__.oe = function(err) { console.error(err); throw err; };
/******/
/******/ // Load entry module and return exports
-/******/ return __webpack_require__(__webpack_require__.s = 7);
+/******/ return __webpack_require__(__webpack_require__.s = 23);
/******/ })
/************************************************************************/
/******/ ([
@@ -1615,7 +1613,10 @@ var DOMCMapReaderFactory = function DOMCMapReaderFactoryClosure() {
request.responseType = 'arraybuffer';
}
request.onreadystatechange = function () {
- if (request.readyState === XMLHttpRequest.DONE && (request.status === 200 || request.status === 0)) {
+ if (request.readyState !== XMLHttpRequest.DONE) {
+ return;
+ }
+ if (request.status === 200 || request.status === 0) {
var data;
if (this.isCompressed && request.response) {
data = new Uint8Array(request.response);
@@ -1629,8 +1630,8 @@ var DOMCMapReaderFactory = function DOMCMapReaderFactoryClosure() {
});
return;
}
- reject(new Error('Unable to load ' + (this.isCompressed ? 'binary ' : '') + 'CMap at: ' + url));
}
+ reject(new Error('Unable to load ' + (this.isCompressed ? 'binary ' : '') + 'CMap at: ' + url));
}.bind(this);
request.send(null);
}.bind(this));
@@ -1670,6 +1671,16 @@ var CustomStyle = function CustomStyleClosure() {
};
return CustomStyle;
}();
+var RenderingCancelledException = function RenderingCancelledException() {
+ function RenderingCancelledException(msg, type) {
+ this.message = msg;
+ this.type = type;
+ }
+ RenderingCancelledException.prototype = new Error();
+ RenderingCancelledException.prototype.name = 'RenderingCancelledException';
+ RenderingCancelledException.constructor = RenderingCancelledException;
+ return RenderingCancelledException;
+}();
var hasCanvasTypedArrays;
hasCanvasTypedArrays = function hasCanvasTypedArrays() {
var canvas = document.createElement('canvas');
@@ -1762,6 +1773,8 @@ function getDefaultSetting(id) {
return globalSettings ? globalSettings.externalLinkRel : DEFAULT_LINK_REL;
case 'enableStats':
return !!(globalSettings && globalSettings.enableStats);
+ case 'pdfjsNext':
+ return !!(globalSettings && globalSettings.pdfjsNext);
default:
throw new Error('Unknown default setting: ' + id);
}
@@ -1789,6 +1802,7 @@ exports.isExternalLinkTargetSet = isExternalLinkTargetSet;
exports.isValidUrl = isValidUrl;
exports.getFilenameFromUrl = getFilenameFromUrl;
exports.LinkTarget = LinkTarget;
+exports.RenderingCancelledException = RenderingCancelledException;
exports.hasCanvasTypedArrays = hasCanvasTypedArrays;
exports.getDefaultSetting = getDefaultSetting;
exports.DEFAULT_LINK_REL = DEFAULT_LINK_REL;
@@ -2450,6 +2464,7 @@ var FontFaceObject = displayFontLoader.FontFaceObject;
var FontLoader = displayFontLoader.FontLoader;
var CanvasGraphics = displayCanvas.CanvasGraphics;
var Metadata = displayMetadata.Metadata;
+var RenderingCancelledException = displayDOMUtils.RenderingCancelledException;
var getDefaultSetting = displayDOMUtils.getDefaultSetting;
var DOMCanvasFactory = displayDOMUtils.DOMCanvasFactory;
var DOMCMapReaderFactory = displayDOMUtils.DOMCMapReaderFactory;
@@ -3711,7 +3726,11 @@ var InternalRenderTask = function InternalRenderTaskClosure() {
cancel: function InternalRenderTask_cancel() {
this.running = false;
this.cancelled = true;
- this.callback('cancelled');
+ if (getDefaultSetting('pdfjsNext')) {
+ this.callback(new RenderingCancelledException('Rendering cancelled, page ' + this.pageNumber, 'canvas'));
+ } else {
+ this.callback('cancelled');
+ }
},
operatorListChanged: function InternalRenderTask_operatorListChanged() {
if (!this.graphicsReady) {
@@ -3776,8 +3795,8 @@ var _UnsupportedManager = function UnsupportedManagerClosure() {
}
};
}();
-exports.version = '1.7.395';
-exports.build = '07f7c97b';
+exports.version = '1.8.172';
+exports.build = '8ff1fbe7';
exports.getDocument = getDocument;
exports.PDFDataRangeTransport = PDFDataRangeTransport;
exports.PDFWorker = PDFWorker;
@@ -5716,8 +5735,8 @@ if (!globalScope.PDFJS) {
globalScope.PDFJS = {};
}
var PDFJS = globalScope.PDFJS;
-PDFJS.version = '1.7.395';
-PDFJS.build = '07f7c97b';
+PDFJS.version = '1.8.172';
+PDFJS.build = '8ff1fbe7';
PDFJS.pdfBug = false;
if (PDFJS.verbosity !== undefined) {
sharedUtil.setVerbosityLevel(PDFJS.verbosity);
@@ -5777,6 +5796,7 @@ PDFJS.disableWebGL = PDFJS.disableWebGL === undefined ? true : PDFJS.disableWebG
PDFJS.externalLinkTarget = PDFJS.externalLinkTarget === undefined ? LinkTarget.NONE : PDFJS.externalLinkTarget;
PDFJS.externalLinkRel = PDFJS.externalLinkRel === undefined ? DEFAULT_LINK_REL : PDFJS.externalLinkRel;
PDFJS.isEvalSupported = PDFJS.isEvalSupported === undefined ? true : PDFJS.isEvalSupported;
+PDFJS.pdfjsNext = PDFJS.pdfjsNext === undefined ? false : PDFJS.pdfjsNext;
var savedOpenExternalLinksInNewWindow = PDFJS.openExternalLinksInNewWindow;
delete PDFJS.openExternalLinksInNewWindow;
Object.defineProperty(PDFJS, 'openExternalLinksInNewWindow', {
@@ -8227,8 +8247,8 @@ exports.TilingPattern = TilingPattern;
"use strict";
-var pdfjsVersion = '1.7.395';
-var pdfjsBuild = '07f7c97b';
+var pdfjsVersion = '1.8.172';
+var pdfjsBuild = '8ff1fbe7';
var pdfjsSharedUtil = __w_pdfjs_require__(0);
var pdfjsDisplayGlobal = __w_pdfjs_require__(9);
var pdfjsDisplayAPI = __w_pdfjs_require__(3);
@@ -8259,6 +8279,7 @@ exports.createObjectURL = pdfjsSharedUtil.createObjectURL;
exports.removeNullCharacters = pdfjsSharedUtil.removeNullCharacters;
exports.shadow = pdfjsSharedUtil.shadow;
exports.createBlob = pdfjsSharedUtil.createBlob;
+exports.RenderingCancelledException = pdfjsDisplayDOMUtils.RenderingCancelledException;
exports.getFilenameFromUrl = pdfjsDisplayDOMUtils.getFilenameFromUrl;
exports.addLinkAttributes = pdfjsDisplayDOMUtils.addLinkAttributes;
@@ -8740,20 +8761,28 @@ if (typeof PDFJS === 'undefined' || !PDFJS.compatibilityChecked) {
}
})();
(function checkRequestAnimationFrame() {
- function fakeRequestAnimationFrame(callback) {
- window.setTimeout(callback, 20);
+ function installFakeAnimationFrameFunctions() {
+ window.requestAnimationFrame = function (callback) {
+ return window.setTimeout(callback, 20);
+ };
+ window.cancelAnimationFrame = function (timeoutID) {
+ window.clearTimeout(timeoutID);
+ };
}
if (!hasDOM) {
return;
}
if (isIOS) {
- window.requestAnimationFrame = fakeRequestAnimationFrame;
+ installFakeAnimationFrameFunctions();
return;
}
if ('requestAnimationFrame' in window) {
return;
}
- window.requestAnimationFrame = window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || fakeRequestAnimationFrame;
+ window.requestAnimationFrame = window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame;
+ if (!('requestAnimationFrame' in window)) {
+ installFakeAnimationFrameFunctions();
+ }
})();
(function checkCanvasSizeLimitation() {
if (isIOS || isAndroid) {
@@ -9760,7 +9789,7 @@ function toComment(sourceMap) {
return '/*# ' + data + ' */';
}
-/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(11).Buffer))
+/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(10).Buffer))
/***/ }),
/* 4 */
@@ -9839,7 +9868,7 @@ if (typeof DEBUG !== 'undefined' && DEBUG) {
) }
}
-var listToStyles = __webpack_require__(23)
+var listToStyles = __webpack_require__(21)
/*
type StyleObject = {
@@ -10046,34 +10075,18 @@ function applyToTag (styleElement, obj) {
/* styles */
-__webpack_require__(21)
+__webpack_require__(19)
var Component = __webpack_require__(4)(
/* script */
- __webpack_require__(8),
+ __webpack_require__(7),
/* template */
- __webpack_require__(19),
+ __webpack_require__(17),
/* scopeId */
null,
/* cssModules */
null
)
-Component.options.__file = "/Users/samrose/Projects/pdflab/src/index.vue"
-if (Component.esModule && Object.keys(Component.esModule).some(function (key) {return key !== "default" && key !== "__esModule"})) {console.error("named exports are not supported in *.vue files.")}
-if (Component.options.functional) {console.error("[vue-loader] index.vue: functional components are not supported with templates, they should use render functions.")}
-
-/* hot reload */
-if (false) {(function () {
- var hotAPI = require("vue-hot-reload-api")
- hotAPI.install(require("vue"), false)
- if (!hotAPI.compatible) return
- module.hot.accept()
- if (!module.hot.data) {
- hotAPI.createRecord("data-v-7c7bed7e", Component.options)
- } else {
- hotAPI.reload("data-v-7c7bed7e", Component.options)
- }
-})()}
module.exports = Component.exports
@@ -10085,25 +10098,6 @@ module.exports = Component.exports
"use strict";
-var PDF = __webpack_require__(6);
-var pdfjsLib = __webpack_require__(2);
-
-module.exports = {
- install: function install(_vue, _ref) {
- var workerSrc = _ref.workerSrc;
-
- pdfjsLib.PDFJS.workerSrc = workerSrc;
- _vue.component('pdf-lab', PDF);
- }
-};
-
-/***/ }),
-/* 8 */
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
Object.defineProperty(exports, "__esModule", {
value: true
});
@@ -10112,7 +10106,7 @@ var _pdfjsDist = __webpack_require__(2);
var _pdfjsDist2 = _interopRequireDefault(_pdfjsDist);
-var _index = __webpack_require__(18);
+var _index = __webpack_require__(16);
var _index2 = _interopRequireDefault(_index);
@@ -10138,7 +10132,7 @@ exports.default = {
},
data: function data() {
return {
- isLoading: false,
+ loading: false,
pages: []
};
},
@@ -10163,17 +10157,17 @@ exports.default = {
}).catch(function (error) {
return _this.$emit('pdflaberror', error);
}).then(function () {
- return _this.isLoading = false;
+ _this.loading = false;
});
},
renderPages: function renderPages(pdf) {
var _this2 = this;
var pagePromises = [];
- this.isLoading = true;
- for (var num = 1; num <= pdf.numPages; num++) {
- pagePromises.push(pdf.getPage(num).then(function (page) {
- return _this2.pages.push(page);
+ this.loading = true;
+ for (var num = 1; num <= pdf.numPages; num += 1) {
+ pagePromises.push(pdf.getPage(num).then(function (p) {
+ return _this2.pages.push(p);
}));
}
return Promise.all(pagePromises);
@@ -10185,7 +10179,7 @@ exports.default = {
};
/***/ }),
-/* 9 */
+/* 8 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -10213,10 +10207,16 @@ exports.default = {
required: true
}
},
+ data: function data() {
+ return {
+ scale: 4,
+ rendering: false
+ };
+ },
+
computed: {
viewport: function viewport() {
- var scale = 4;
- return this.page.getViewport(scale);
+ return this.page.getViewport(this.scale);
},
context: function context() {
return this.$refs.canvas.getContext('2d');
@@ -10229,14 +10229,19 @@ exports.default = {
}
},
mounted: function mounted() {
+ var _this = this;
+
this.$refs.canvas.height = this.viewport.height;
this.$refs.canvas.width = this.viewport.width;
- this.page.render(this.renderContext);
+ this.rendering = true;
+ this.page.render(this.renderContext).then(function () {
+ _this.rendering = false;
+ });
}
};
/***/ }),
-/* 10 */
+/* 9 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -10357,7 +10362,7 @@ function fromByteArray (uint8) {
/***/ }),
-/* 11 */
+/* 10 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -10371,9 +10376,9 @@ function fromByteArray (uint8) {
-var base64 = __webpack_require__(10)
-var ieee754 = __webpack_require__(14)
-var isArray = __webpack_require__(15)
+var base64 = __webpack_require__(9)
+var ieee754 = __webpack_require__(13)
+var isArray = __webpack_require__(14)
exports.Buffer = Buffer
exports.SlowBuffer = SlowBuffer
@@ -12151,10 +12156,10 @@ function isnan (val) {
return val !== val // eslint-disable-line no-self-compare
}
-/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(24)))
+/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(22)))
/***/ }),
-/* 12 */
+/* 11 */
/***/ (function(module, exports, __webpack_require__) {
exports = module.exports = __webpack_require__(3)(undefined);
@@ -12162,13 +12167,13 @@ exports = module.exports = __webpack_require__(3)(undefined);
// module
-exports.push([module.i, "\n.pdf-viewer {\n background: url(" + __webpack_require__(17) + ");\n display: flex;\n flex-flow: column nowrap;\n}\n", ""]);
+exports.push([module.i, ".pdf-viewer{background:url(" + __webpack_require__(15) + ");display:flex;flex-flow:column nowrap}", ""]);
// exports
/***/ }),
-/* 13 */
+/* 12 */
/***/ (function(module, exports, __webpack_require__) {
exports = module.exports = __webpack_require__(3)(undefined);
@@ -12176,13 +12181,13 @@ exports = module.exports = __webpack_require__(3)(undefined);
// module
-exports.push([module.i, "\n.pdf-page {\n margin: 8px auto 0 auto;\n border-top: 1px #ddd solid;\n border-bottom: 1px #ddd solid;\n width: 100%;\n}\n.pdf-page:first-child {\n margin-top: 0px;\n border-top: 0px;\n}\n.pdf-page:last-child {\n margin-bottom: 0px;\n border-bottom: 0px;\n}\n", ""]);
+exports.push([module.i, ".pdf-page{margin:8px auto 0;border-top:1px solid #ddd;border-bottom:1px solid #ddd;width:100%}.pdf-page:first-child{margin-top:0;border-top:0}.pdf-page:last-child{margin-bottom:0;border-bottom:0}", ""]);
// exports
/***/ }),
-/* 14 */
+/* 13 */
/***/ (function(module, exports) {
exports.read = function (buffer, offset, isLE, mLen, nBytes) {
@@ -12272,7 +12277,7 @@ exports.write = function (buffer, value, offset, isLE, mLen, nBytes) {
/***/ }),
-/* 15 */
+/* 14 */
/***/ (function(module, exports) {
var toString = {}.toString;
@@ -12283,53 +12288,36 @@ module.exports = Array.isArray || function (arr) {
/***/ }),
-/* 16 */,
-/* 17 */
+/* 15 */
/***/ (function(module, exports) {
module.exports = "data:image/gif;base64,R0lGODlhCgAKAIAAAOXl5f///yH5BAAAAAAALAAAAAAKAAoAAAIRhB2ZhxoM3GMSykqd1VltzxQAOw=="
/***/ }),
-/* 18 */
+/* 16 */
/***/ (function(module, exports, __webpack_require__) {
/* styles */
-__webpack_require__(22)
+__webpack_require__(20)
var Component = __webpack_require__(4)(
/* script */
- __webpack_require__(9),
+ __webpack_require__(8),
/* template */
- __webpack_require__(20),
+ __webpack_require__(18),
/* scopeId */
null,
/* cssModules */
null
)
-Component.options.__file = "/Users/samrose/Projects/pdflab/src/page/index.vue"
-if (Component.esModule && Object.keys(Component.esModule).some(function (key) {return key !== "default" && key !== "__esModule"})) {console.error("named exports are not supported in *.vue files.")}
-if (Component.options.functional) {console.error("[vue-loader] index.vue: functional components are not supported with templates, they should use render functions.")}
-
-/* hot reload */
-if (false) {(function () {
- var hotAPI = require("vue-hot-reload-api")
- hotAPI.install(require("vue"), false)
- if (!hotAPI.compatible) return
- module.hot.accept()
- if (!module.hot.data) {
- hotAPI.createRecord("data-v-7e912b1a", Component.options)
- } else {
- hotAPI.reload("data-v-7e912b1a", Component.options)
- }
-})()}
module.exports = Component.exports
/***/ }),
-/* 19 */
-/***/ (function(module, exports, __webpack_require__) {
+/* 17 */
+/***/ (function(module, exports) {
module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;
return (_vm.hasPDF) ? _c('div', {
@@ -12338,24 +12326,17 @@ module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c
return _c('page', {
key: index,
attrs: {
- "v-if": !_vm.isLoading,
+ "v-if": !_vm.loading,
"page": page,
"number": index + 1
}
})
})) : _vm._e()
},staticRenderFns: []}
-module.exports.render._withStripped = true
-if (false) {
- module.hot.accept()
- if (module.hot.data) {
- require("vue-hot-reload-api").rerender("data-v-7c7bed7e", module.exports)
- }
-}
/***/ }),
-/* 20 */
-/***/ (function(module, exports, __webpack_require__) {
+/* 18 */
+/***/ (function(module, exports) {
module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;
return _c('canvas', {
@@ -12366,32 +12347,25 @@ module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c
}
})
},staticRenderFns: []}
-module.exports.render._withStripped = true
-if (false) {
- module.hot.accept()
- if (module.hot.data) {
- require("vue-hot-reload-api").rerender("data-v-7e912b1a", module.exports)
- }
-}
/***/ }),
-/* 21 */
+/* 19 */
/***/ (function(module, exports, __webpack_require__) {
// style-loader: Adds some css to the DOM by adding a <style> tag
// load the styles
-var content = __webpack_require__(12);
+var content = __webpack_require__(11);
if(typeof content === 'string') content = [[module.i, content, '']];
if(content.locals) module.exports = content.locals;
// add the styles to the DOM
-var update = __webpack_require__(5)("8018213c", content, false);
+var update = __webpack_require__(5)("59cf066f", content, true);
// Hot Module Replacement
if(false) {
// When the styles change, update the <style> tags
if(!content.locals) {
- module.hot.accept("!!../node_modules/css-loader/index.js!../node_modules/vue-loader/lib/style-compiler/index.js?{\"id\":\"data-v-7c7bed7e\",\"scoped\":false,\"hasInlineConfig\":false}!../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./index.vue", function() {
- var newContent = require("!!../node_modules/css-loader/index.js!../node_modules/vue-loader/lib/style-compiler/index.js?{\"id\":\"data-v-7c7bed7e\",\"scoped\":false,\"hasInlineConfig\":false}!../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./index.vue");
+ module.hot.accept("!!../node_modules/css-loader/index.js?minimize!../node_modules/vue-loader/lib/style-compiler/index.js?{\"id\":\"data-v-7c7bed7e\",\"scoped\":false,\"hasInlineConfig\":false}!../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./index.vue", function() {
+ var newContent = require("!!../node_modules/css-loader/index.js?minimize!../node_modules/vue-loader/lib/style-compiler/index.js?{\"id\":\"data-v-7c7bed7e\",\"scoped\":false,\"hasInlineConfig\":false}!../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./index.vue");
if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];
update(newContent);
});
@@ -12401,23 +12375,23 @@ if(false) {
}
/***/ }),
-/* 22 */
+/* 20 */
/***/ (function(module, exports, __webpack_require__) {
// style-loader: Adds some css to the DOM by adding a <style> tag
// load the styles
-var content = __webpack_require__(13);
+var content = __webpack_require__(12);
if(typeof content === 'string') content = [[module.i, content, '']];
if(content.locals) module.exports = content.locals;
// add the styles to the DOM
-var update = __webpack_require__(5)("6d9dea59", content, false);
+var update = __webpack_require__(5)("09f1e2d8", content, true);
// Hot Module Replacement
if(false) {
// When the styles change, update the <style> tags
if(!content.locals) {
- module.hot.accept("!!../../node_modules/css-loader/index.js!../../node_modules/vue-loader/lib/style-compiler/index.js?{\"id\":\"data-v-7e912b1a\",\"scoped\":false,\"hasInlineConfig\":false}!../../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./index.vue", function() {
- var newContent = require("!!../../node_modules/css-loader/index.js!../../node_modules/vue-loader/lib/style-compiler/index.js?{\"id\":\"data-v-7e912b1a\",\"scoped\":false,\"hasInlineConfig\":false}!../../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./index.vue");
+ module.hot.accept("!!../../node_modules/css-loader/index.js?minimize!../../node_modules/vue-loader/lib/style-compiler/index.js?{\"id\":\"data-v-7e912b1a\",\"scoped\":false,\"hasInlineConfig\":false}!../../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./index.vue", function() {
+ var newContent = require("!!../../node_modules/css-loader/index.js?minimize!../../node_modules/vue-loader/lib/style-compiler/index.js?{\"id\":\"data-v-7e912b1a\",\"scoped\":false,\"hasInlineConfig\":false}!../../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./index.vue");
if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];
update(newContent);
});
@@ -12427,7 +12401,7 @@ if(false) {
}
/***/ }),
-/* 23 */
+/* 21 */
/***/ (function(module, exports) {
/**
@@ -12460,7 +12434,7 @@ module.exports = function listToStyles (parentId, list) {
/***/ }),
-/* 24 */
+/* 22 */
/***/ (function(module, exports) {
var g;
@@ -12486,6 +12460,25 @@ try {
module.exports = g;
+/***/ }),
+/* 23 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+var PDF = __webpack_require__(6);
+var pdfjsLib = __webpack_require__(2);
+
+module.exports = {
+ install: function install(_vue) {
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
+
+ pdfjsLib.PDFJS.workerSrc = options.workerSrc || '';
+ _vue.component('pdf-lab', PDF);
+ }
+};
+
/***/ })
/******/ ]);
}); \ No newline at end of file
diff --git a/vendor/gitignore/C.gitignore b/vendor/gitignore/C.gitignore
index 8a365b3d829..c6127b38c1a 100644
--- a/vendor/gitignore/C.gitignore
+++ b/vendor/gitignore/C.gitignore
@@ -45,6 +45,7 @@
# Kernel Module Compile Results
*.mod*
*.cmd
+.tmp_versions/
modules.order
Module.symvers
Mkfile.old
diff --git a/vendor/gitignore/Dart.gitignore b/vendor/gitignore/Dart.gitignore
index 4b366585ddc..4d2a4d6db7c 100644
--- a/vendor/gitignore/Dart.gitignore
+++ b/vendor/gitignore/Dart.gitignore
@@ -1,33 +1,12 @@
# See https://www.dartlang.org/tools/private-files.html
# Files and directories created by pub
-
-# SDK 1.20 and later (no longer creates packages directories)
.packages
.pub/
build/
-
-# Older SDK versions
-# (Include if the minimum SDK version specified in pubsepc.yaml is earlier than 1.20)
-.project
-.buildlog
-**/packages/
-
-
-# Files created by dart2js
-# (Most Dart developers will use pub build to compile Dart, use/modify these
-# rules if you intend to use dart2js directly
-# Convention is to use extension '.dart.js' for Dart compiled to Javascript to
-# differentiate from explicit Javascript files)
-*.dart.js
-*.part.js
-*.js.deps
-*.js.map
-*.info.json
+# If you're building an application, you may want to check-in your pubspec.lock
+pubspec.lock
# Directory created by dartdoc
+# If you don't generate documentation locally you can remove this line.
doc/api/
-
-# Don't commit pubspec lock file
-# (Library packages only! Remove pattern if developing an application package)
-pubspec.lock
diff --git a/vendor/gitignore/Global/Eclipse.gitignore b/vendor/gitignore/Global/Eclipse.gitignore
index 4f88399d2d8..ce1c12cdb7a 100644
--- a/vendor/gitignore/Global/Eclipse.gitignore
+++ b/vendor/gitignore/Global/Eclipse.gitignore
@@ -11,9 +11,6 @@ local.properties
.loadpath
.recommenders
-# Eclipse Core
-.project
-
# External tool builders
.externalToolBuilders/
@@ -26,9 +23,6 @@ local.properties
# CDT-specific (C/C++ Development Tooling)
.cproject
-# JDT-specific (Eclipse Java Development Tools)
-.classpath
-
# Java annotation processor (APT)
.factorypath
diff --git a/vendor/gitignore/Global/JetBrains.gitignore b/vendor/gitignore/Global/JetBrains.gitignore
index ec7e95c6ab5..a5d4cc86d33 100644
--- a/vendor/gitignore/Global/JetBrains.gitignore
+++ b/vendor/gitignore/Global/JetBrains.gitignore
@@ -36,6 +36,9 @@
# JIRA plugin
atlassian-ide-plugin.xml
+# Cursive Clojure plugin
+.idea/replstate.xml
+
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
diff --git a/vendor/gitignore/Global/macOS.gitignore b/vendor/gitignore/Global/macOS.gitignore
index f0f3fbc06c8..5972fe50f66 100644
--- a/vendor/gitignore/Global/macOS.gitignore
+++ b/vendor/gitignore/Global/macOS.gitignore
@@ -1,26 +1,25 @@
-*.DS_Store
-.AppleDouble
-.LSOverride
-
-# Icon must end with two \r
-Icon
-
-
-# Thumbnails
-._*
-
-# Files that might appear in the root of a volume
-.DocumentRevisions-V100
-.fseventsd
-.Spotlight-V100
-.TemporaryItems
-.Trashes
-.VolumeIcon.icns
-.com.apple.timemachine.donotpresent
-
-# Directories potentially created on remote AFP share
-.AppleDB
-.AppleDesktop
-Network Trash Folder
-Temporary Items
-.apdisk
+*.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
diff --git a/vendor/gitignore/Python.gitignore b/vendor/gitignore/Python.gitignore
index 62c1e736924..ff65a437185 100644
--- a/vendor/gitignore/Python.gitignore
+++ b/vendor/gitignore/Python.gitignore
@@ -92,3 +92,6 @@ ENV/
# Rope project settings
.ropeproject
+
+# mkdocs documentation
+/site
diff --git a/vendor/gitignore/Rails.gitignore b/vendor/gitignore/Rails.gitignore
index e97427608c1..42aeb55000a 100644
--- a/vendor/gitignore/Rails.gitignore
+++ b/vendor/gitignore/Rails.gitignore
@@ -8,7 +8,7 @@ capybara-*.html
/public/system
/coverage/
/spec/tmp
-**.orig
+*.orig
rerun.txt
pickle-email-*.html
diff --git a/vendor/gitignore/TeX.gitignore b/vendor/gitignore/TeX.gitignore
index 57ed9f5d972..a0322dbd35a 100644
--- a/vendor/gitignore/TeX.gitignore
+++ b/vendor/gitignore/TeX.gitignore
@@ -148,6 +148,9 @@ _minted*
# pax
*.pax
+# pdfpcnotes
+*.pdfpc
+
# sagetex
*.sagetex.sage
*.sagetex.py
diff --git a/vendor/gitignore/Unity.gitignore b/vendor/gitignore/Unity.gitignore
index b829399ae85..eb83a8f122d 100644
--- a/vendor/gitignore/Unity.gitignore
+++ b/vendor/gitignore/Unity.gitignore
@@ -23,7 +23,6 @@ ExportedObj/
*.svd
*.pdb
-
# Unity3D generated meta files
*.pidb.meta
diff --git a/vendor/gitignore/VisualStudio.gitignore b/vendor/gitignore/VisualStudio.gitignore
index a752eacca7d..940794e60f2 100644
--- a/vendor/gitignore/VisualStudio.gitignore
+++ b/vendor/gitignore/VisualStudio.gitignore
@@ -219,6 +219,7 @@ UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
+*.ndf
# Business Intelligence projects
*.rdl.data
@@ -284,4 +285,4 @@ __pycache__/
*.btp.cs
*.btm.cs
*.odx.cs
-*.xsd.cs \ No newline at end of file
+*.xsd.cs
diff --git a/vendor/gitlab-ci-yml/CONTRIBUTING.md b/vendor/gitlab-ci-yml/CONTRIBUTING.md
new file mode 100644
index 00000000000..6e5160a2487
--- /dev/null
+++ b/vendor/gitlab-ci-yml/CONTRIBUTING.md
@@ -0,0 +1,5 @@
+The canonical repository for `.gitlab-ci.yml` templates is
+https://gitlab.com/gitlab-org/gitlab-ci-yml.
+
+GitLab only mirrors the templates. Please submit your merge requests to
+https://gitlab.com/gitlab-org/gitlab-ci-yml.
diff --git a/vendor/gitlab-ci-yml/Django.gitlab-ci.yml b/vendor/gitlab-ci-yml/Django.gitlab-ci.yml
index b3106863cca..5ded2f5ce76 100644
--- a/vendor/gitlab-ci-yml/Django.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Django.gitlab-ci.yml
@@ -26,9 +26,24 @@ before_script:
# - apt-get update -q && apt-get install nodejs -yqq
- pip install -r requirements.txt
+# To get Django tests to work you may need to create a settings file using
+# the following DATABASES:
+#
+# DATABASES = {
+# 'default': {
+# 'ENGINE': 'django.db.backends.postgresql_psycopg2',
+# 'NAME': 'ci',
+# 'USER': 'postgres',
+# 'PASSWORD': 'postgres',
+# 'HOST': 'postgres',
+# 'PORT': '5432',
+# },
+# }
+#
+# and then adding `--settings app.settings.ci` (or similar) to the test command
+
test:
variables:
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB"
script:
- - python manage.py migrate
- python manage.py test
diff --git a/vendor/gitlab-ci-yml/Pages/Hexo.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Hexo.gitlab-ci.yml
index 908463c9d12..02d02250bbf 100644
--- a/vendor/gitlab-ci-yml/Pages/Hexo.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Pages/Hexo.gitlab-ci.yml
@@ -1,17 +1,16 @@
# Full project: https://gitlab.com/pages/hexo
-image: node:4.2.2
+image: node:6.10.0
pages:
- cache:
- paths:
- - node_modules/
-
script:
- - npm install hexo-cli -g
- npm install
- - hexo deploy
+ - ./node_modules/hexo/bin/hexo generate
artifacts:
paths:
- public
+ cache:
+ paths:
+ - node_modules
+ key: project
only:
- master
diff --git a/vendor/gitlab-ci-yml/Pages/Jekyll.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Jekyll.gitlab-ci.yml
index d98cf94d635..37f50554036 100644
--- a/vendor/gitlab-ci-yml/Pages/Jekyll.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Pages/Jekyll.gitlab-ci.yml
@@ -1,8 +1,10 @@
# Template project: https://gitlab.com/pages/jekyll
# Docs: https://docs.gitlab.com/ce/pages/
-# Jekyll version: 3.4.0
image: ruby:2.3
+variables:
+ JEKYLL_ENV: production
+
before_script:
- bundle install
@@ -25,4 +27,4 @@ pages:
- public
only:
- master
- \ No newline at end of file
+
diff --git a/vendor/gitlab-ci-yml/Scala.gitlab-ci.yml b/vendor/gitlab-ci-yml/Scala.gitlab-ci.yml
index 443ba42e38c..b4208ed9d7d 100644
--- a/vendor/gitlab-ci-yml/Scala.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Scala.gitlab-ci.yml
@@ -9,7 +9,7 @@ before_script:
- apt-get install apt-transport-https -yqq
# Add keyserver for SBT
- echo "deb http://dl.bintray.com/sbt/debian /" | tee -a /etc/apt/sources.list.d/sbt.list
- - apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 642AC823
+ - apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 2EE0EA64E40A89B84B2DF73499E82A75642AC823
# Install SBT
- apt-get update -yqq
- apt-get install sbt -yqq
diff --git a/vendor/licenses.csv b/vendor/licenses.csv
index a2cbef126ad..6441df25fe1 100644
--- a/vendor/licenses.csv
+++ b/vendor/licenses.csv
@@ -1,9 +1,9 @@
RedCloth,4.3.2,MIT
abbrev,1.0.9,ISC
accepts,1.3.3,MIT
-ace-rails-ap,4.1.0,MIT
-acorn,4.0.4,MIT
-acorn-dynamic-import,2.0.1,MIT
+ace-rails-ap,4.1.2,MIT
+acorn,4.0.11,MIT
+acorn-dynamic-import,2.0.2,MIT
acorn-jsx,3.0.1,MIT
actionmailer,4.2.8,MIT
actionpack,4.2.8,MIT
@@ -16,19 +16,20 @@ acts-as-taggable-on,4.0.0,MIT
addressable,2.3.8,Apache 2.0
after,0.8.2,MIT
after_commit_queue,1.3.0,MIT
-ajv,4.11.2,MIT
+ajv,4.11.5,MIT
ajv-keywords,1.5.1,MIT
akismet,2.0.0,MIT
align-text,0.1.4,MIT
allocations,1.0.5,MIT
+alphanum-sort,1.0.2,MIT
amdefine,1.0.1,BSD-3-Clause OR MIT
ansi-escapes,1.4.0,MIT
-ansi-html,0.0.7,Apache 2.0
+ansi-html,0.0.5,"Apache, Version 2.0"
ansi-regex,2.1.1,MIT
ansi-styles,2.2.1,MIT
anymatch,1.3.0,ISC
append-transform,0.4.0,MIT
-aproba,1.1.0,ISC
+aproba,1.1.1,ISC
are-we-there-yet,1.1.2,ISC
arel,6.0.4,MIT
argparse,1.0.9,MIT
@@ -55,13 +56,14 @@ asynckit,0.4.0,MIT
attr_encrypted,3.0.3,MIT
attr_required,1.0.0,MIT
autoparse,0.3.3,Apache 2.0
+autoprefixer,6.7.7,MIT
autoprefixer-rails,6.2.3,MIT
aws-sign2,0.6.0,Apache 2.0
aws4,1.6.0,MIT
axiom-types,0.1.1,MIT
babel-code-frame,6.22.0,MIT
-babel-core,6.23.1,MIT
-babel-generator,6.23.0,MIT
+babel-core,6.24.0,MIT
+babel-generator,6.24.0,MIT
babel-helper-bindify-decorators,6.22.0,MIT
babel-helper-builder-binary-assignment-operator-visitor,6.22.0,MIT
babel-helper-call-delegate,6.22.0,MIT
@@ -76,10 +78,10 @@ babel-helper-regex,6.22.0,MIT
babel-helper-remap-async-to-generator,6.22.0,MIT
babel-helper-replace-supers,6.23.0,MIT
babel-helpers,6.23.0,MIT
-babel-loader,6.2.10,MIT
+babel-loader,6.4.1,MIT
babel-messages,6.23.0,MIT
babel-plugin-check-es2015-constants,6.22.0,MIT
-babel-plugin-istanbul,4.0.0,New BSD
+babel-plugin-istanbul,4.1.1,New BSD
babel-plugin-syntax-async-functions,6.13.0,MIT
babel-plugin-syntax-async-generators,6.13.0,MIT
babel-plugin-syntax-class-properties,6.13.0,MIT
@@ -92,6 +94,7 @@ babel-plugin-transform-async-generator-functions,6.22.0,MIT
babel-plugin-transform-async-to-generator,6.22.0,MIT
babel-plugin-transform-class-properties,6.23.0,MIT
babel-plugin-transform-decorators,6.22.0,MIT
+babel-plugin-transform-define,1.2.0,MIT
babel-plugin-transform-es2015-arrow-functions,6.22.0,MIT
babel-plugin-transform-es2015-block-scoped-functions,6.22.0,MIT
babel-plugin-transform-es2015-block-scoping,6.23.0,MIT
@@ -102,10 +105,10 @@ babel-plugin-transform-es2015-duplicate-keys,6.22.0,MIT
babel-plugin-transform-es2015-for-of,6.23.0,MIT
babel-plugin-transform-es2015-function-name,6.22.0,MIT
babel-plugin-transform-es2015-literals,6.22.0,MIT
-babel-plugin-transform-es2015-modules-amd,6.22.0,MIT
-babel-plugin-transform-es2015-modules-commonjs,6.23.0,MIT
+babel-plugin-transform-es2015-modules-amd,6.24.0,MIT
+babel-plugin-transform-es2015-modules-commonjs,6.24.0,MIT
babel-plugin-transform-es2015-modules-systemjs,6.23.0,MIT
-babel-plugin-transform-es2015-modules-umd,6.23.0,MIT
+babel-plugin-transform-es2015-modules-umd,6.24.0,MIT
babel-plugin-transform-es2015-object-super,6.22.0,MIT
babel-plugin-transform-es2015-parameters,6.23.0,MIT
babel-plugin-transform-es2015-shorthand-properties,6.22.0,MIT
@@ -118,16 +121,19 @@ babel-plugin-transform-exponentiation-operator,6.22.0,MIT
babel-plugin-transform-object-rest-spread,6.23.0,MIT
babel-plugin-transform-regenerator,6.22.0,MIT
babel-plugin-transform-strict-mode,6.22.0,MIT
-babel-preset-es2015,6.22.0,MIT
+babel-preset-es2015,6.24.0,MIT
+babel-preset-es2016,6.22.0,MIT
+babel-preset-es2017,6.22.0,MIT
+babel-preset-latest,6.24.0,MIT
babel-preset-stage-2,6.22.0,MIT
babel-preset-stage-3,6.22.0,MIT
-babel-register,6.23.0,MIT
-babel-runtime,6.22.0,MIT
+babel-register,6.24.0,MIT
+babel-runtime,6.23.0,MIT
babel-template,6.23.0,MIT
babel-traverse,6.23.1,MIT
babel-types,6.23.0,MIT
babosa,1.0.2,MIT
-babylon,6.15.0,MIT
+babylon,6.16.1,MIT
backo2,1.0.2,MIT
balanced-match,0.4.2,MIT
base32,0.3.2,MIT
@@ -143,21 +149,22 @@ binary-extensions,1.8.0,MIT
bindata,2.3.5,ruby
blob,0.0.4,unknown
block-stream,0.0.9,ISC
-bluebird,3.4.7,MIT
+bluebird,3.5.0,MIT
bn.js,4.11.6,MIT
-body-parser,1.16.0,MIT
+body-parser,1.17.1,MIT
boom,2.10.1,New BSD
bootstrap-sass,3.3.6,MIT
brace-expansion,1.1.6,MIT
braces,1.8.5,MIT
-brorand,1.0.7,MIT
+brorand,1.1.0,MIT
browser,2.2.0,MIT
browserify-aes,1.0.6,MIT
browserify-cipher,1.0.0,MIT
browserify-des,1.0.0,MIT
browserify-rsa,4.0.1,MIT
-browserify-sign,4.0.0,ISC
+browserify-sign,4.0.4,ISC
browserify-zlib,0.1.4,MIT
+browserslist,1.7.7,MIT
buffer,4.9.1,MIT
buffer-shims,1.0.0,MIT
buffer-xor,1.0.3,MIT
@@ -169,8 +176,10 @@ caller-path,0.1.0,MIT
callsite,1.0.0,unknown
callsites,0.2.0,MIT
camelcase,1.2.1,MIT
+caniuse-api,1.6.1,MIT
+caniuse-db,1.0.30000649,CC-BY-4.0
carrierwave,0.11.2,MIT
-caseless,0.11.0,Apache 2.0
+caseless,0.12.0,Apache 2.0
cause,0.1,MIT
center-align,0.1.3,MIT
chalk,1.1.3,MIT
@@ -181,16 +190,24 @@ chronic_duration,0.10.6,MIT
chunky_png,1.3.5,MIT
cipher-base,1.0.3,MIT
circular-json,0.3.1,MIT
+citrus,3.0.2,MIT
+clap,1.1.3,MIT
cli-cursor,1.0.2,MIT
cli-width,2.1.0,ISC
cliui,2.1.0,ISC
clone,1.0.2,MIT
co,4.6.0,MIT
+coa,1.0.1,MIT
code-point-at,1.1.0,MIT
coercible,1.0.0,MIT
coffee-rails,4.1.1,MIT
coffee-script,2.4.1,MIT
coffee-script-source,1.10.0,MIT
+color,0.11.4,MIT
+color-convert,1.9.0,MIT
+color-name,1.1.2,MIT
+color-string,0.3.0,MIT
+colormin,1.1.2,MIT
colors,1.1.2,MIT
combine-lists,1.0.1,MIT
combined-stream,1.0.5,MIT
@@ -199,26 +216,29 @@ commondir,1.0.1,MIT
component-bind,1.0.0,unknown
component-emitter,1.2.1,MIT
component-inherit,0.0.3,unknown
-compressible,2.0.9,MIT
+compressible,2.0.10,MIT
compression,1.6.2,MIT
compression-webpack-plugin,0.3.2,MIT
concat-map,0.0.1,MIT
concat-stream,1.6.0,MIT
-concurrent-ruby,1.0.4,MIT
-connect,3.5.0,MIT
+config-chain,1.1.11,MIT
+configstore,1.4.0,Simplified BSD
+connect,3.6.0,MIT
connect-history-api-fallback,1.3.0,MIT
connection_pool,2.2.1,MIT
console-browserify,1.1.0,MIT
console-control-strings,1.1.0,ISC
+consolidate,0.14.5,MIT
constants-browserify,1.0.0,MIT
contains-path,0.1.0,MIT
content-disposition,0.5.2,MIT
content-type,1.0.2,MIT
-convert-source-map,1.3.0,MIT
+convert-source-map,1.5.0,MIT
cookie,0.3.1,MIT
cookie-signature,1.0.6,MIT
core-js,2.4.1,MIT
core-util-is,1.0.2,MIT
+cosmiconfig,2.1.1,MIT
crack,0.4.3,MIT
create-ecdh,4.0.0,MIT
create-hash,1.1.2,MIT
@@ -226,14 +246,21 @@ create-hmac,1.1.4,MIT
creole,0.5.0,ruby
cryptiles,2.0.5,New BSD
crypto-browserify,3.11.0,MIT
+css-color-names,0.0.4,MIT
+css-loader,0.28.0,MIT
+css-selector-tokenizer,0.7.0,MIT
css_parser,1.4.1,MIT
+cssesc,0.1.0,MIT
+cssnano,3.10.0,MIT
+csso,2.3.2,MIT
custom-event,1.0.1,MIT
-d,0.1.1,MIT
-d3,3.5.11,New BSD
+d,1.0.0,MIT
+d3,3.5.17,New BSD
d3_rails,3.5.11,MIT
dashdash,1.14.1,MIT
date-now,0.1.4,MIT
-debug,2.6.0,MIT
+de-indent,1.0.2,MIT
+debug,2.6.3,MIT
decamelize,1.2.0,MIT
deckar01-task_list,1.0.6,MIT
deep-extend,0.4.1,MIT
@@ -241,6 +268,7 @@ deep-is,0.1.3,MIT
default-require-extensions,1.0.0,MIT
default_value_for,3.0.2,MIT
defaults,1.0.3,MIT
+defined,1.0.0,MIT
del,2.2.2,MIT
delayed-stream,1.0.0,MIT
delegates,1.0.0,MIT
@@ -255,62 +283,74 @@ di,0.0.1,MIT
diff-lcs,1.2.5,"MIT,Perl Artistic v2,GNU GPL v2"
diffie-hellman,5.0.2,MIT
diffy,3.1.0,MIT
-doctrine,1.5.0,BSD
-document-register-element,1.3.0,MIT
+doctrine,2.0.0,Apache 2.0
+document-register-element,1.4.1,MIT
dom-serialize,2.2.1,MIT
+dom-serializer,0.1.0,MIT
domain-browser,1.1.7,MIT
domain_name,0.5.20161021,"Simplified BSD,New BSD,Mozilla Public License 2.0"
+domelementtype,1.3.0,unknown
+domhandler,2.3.0,unknown
+domutils,1.5.1,unknown
doorkeeper,4.2.0,MIT
doorkeeper-openid_connect,1.1.2,MIT
-dropzone,4.2.0,MIT
+dropzone,4.3.0,MIT
dropzonejs-rails,0.7.2,MIT
duplexer,0.1.1,MIT
+duplexify,3.5.0,MIT
ecc-jsbn,0.1.1,MIT
+editorconfig,0.13.2,MIT
ee-first,1.1.1,MIT
ejs,2.5.6,Apache 2.0
-elliptic,6.3.3,MIT
+electron-to-chromium,1.3.3,ISC
+elliptic,6.4.0,MIT
email_reply_trimmer,0.1.6,MIT
emoji-unicode-version,0.2.1,MIT
emojis-list,2.1.0,MIT
encodeurl,1.0.1,MIT
encryptor,3.0.0,MIT
-engine.io,1.8.2,MIT
-engine.io-client,1.8.2,MIT
+end-of-stream,1.0.0,MIT
+engine.io,1.8.3,MIT
+engine.io-client,1.8.3,MIT
engine.io-parser,1.3.2,MIT
enhanced-resolve,3.1.0,MIT
ent,2.2.0,MIT
+entities,1.1.1,BSD-like
equalizer,0.0.11,MIT
errno,0.1.4,MIT
-error-ex,1.3.0,MIT
+error-ex,1.3.1,MIT
erubis,2.7.0,MIT
-es5-ext,0.10.12,MIT
-es6-iterator,2.0.0,MIT
-es6-map,0.1.4,MIT
-es6-promise,4.0.5,MIT
-es6-set,0.1.4,MIT
-es6-symbol,3.1.0,MIT
-es6-weak-map,2.0.1,MIT
+es5-ext,0.10.15,MIT
+es6-iterator,2.0.1,MIT
+es6-map,0.1.5,MIT
+es6-promise,3.0.2,MIT
+es6-set,0.1.5,MIT
+es6-symbol,3.1.1,MIT
+es6-weak-map,2.0.2,MIT
escape-html,1.0.3,MIT
escape-string-regexp,1.0.5,MIT
escape_utils,1.1.1,MIT
escodegen,1.8.1,Simplified BSD
escope,3.6.0,Simplified BSD
-eslint,3.15.0,MIT
+eslint,3.19.0,MIT
eslint-config-airbnb-base,10.0.1,MIT
eslint-import-resolver-node,0.2.3,MIT
eslint-import-resolver-webpack,0.8.1,MIT
eslint-module-utils,2.0.0,MIT
eslint-plugin-filenames,1.1.0,MIT
+eslint-plugin-html,2.0.1,ISC
eslint-plugin-import,2.2.0,MIT
eslint-plugin-jasmine,2.2.0,MIT
-espree,3.4.0,Simplified BSD
-esprima,3.1.3,Simplified BSD
+espree,3.4.1,Simplified BSD
+esprima,2.7.3,Simplified BSD
+esquery,1.0.0,BSD
esrecurse,4.1.0,Simplified BSD
estraverse,4.1.1,Simplified BSD
esutils,2.0.2,BSD
-etag,1.7.0,MIT
+etag,1.8.0,MIT
eve-raphael,0.5.0,Apache 2.0
-event-emitter,0.3.4,MIT
+event-emitter,0.3.5,MIT
+event-stream,3.3.4,MIT
eventemitter3,1.2.0,MIT
events,1.1.1,MIT
eventsource,0.1.6,MIT
@@ -321,7 +361,7 @@ exit-hook,1.1.1,MIT
expand-braces,0.1.2,MIT
expand-brackets,0.1.5,MIT
expand-range,1.8.2,MIT
-express,4.14.1,MIT
+express,4.15.2,MIT
expression_parser,0.9.0,MIT
extend,3.0.0,MIT
extglob,0.3.2,MIT
@@ -332,20 +372,23 @@ faraday,0.9.2,MIT
faraday_middleware,0.10.0,MIT
faraday_middleware-multi_json,0.0.6,MIT
fast-levenshtein,2.0.6,MIT
-faye-websocket,0.10.0,MIT
+fastparse,1.1.1,MIT
+faye-websocket,0.7.3,MIT
fd-slicer,1.0.1,MIT
ffi,1.9.10,BSD
figures,1.7.0,MIT
file-entry-cache,2.0.0,MIT
+file-loader,0.11.1,MIT
filename-regex,2.0.0,MIT
fileset,2.0.3,MIT
-filesize,3.5.4,New BSD
+filesize,3.3.0,New BSD
fill-range,2.2.3,MIT
-finalhandler,0.5.1,MIT
+finalhandler,1.0.1,MIT
find-cache-dir,0.1.1,MIT
find-root,0.1.2,MIT
find-up,2.1.0,MIT
flat-cache,1.2.2,MIT
+flatten,1.0.2,MIT
flowdock,0.7.1,MIT
fog-aws,0.11.0,MIT
fog-core,1.42.0,MIT
@@ -356,20 +399,21 @@ fog-openstack,0.1.6,MIT
fog-rackspace,0.1.1,MIT
fog-xml,0.1.2,MIT
font-awesome-rails,4.7.0.1,"MIT,SIL Open Font License"
-for-in,0.1.6,MIT
-for-own,0.1.4,MIT
+for-in,1.0.2,MIT
+for-own,0.1.5,MIT
forever-agent,0.6.1,Apache 2.0
form-data,2.1.2,MIT
formatador,0.2.5,MIT
forwarded,0.1.0,MIT
-fresh,0.3.0,MIT
+fresh,0.5.0,MIT
+from,0.1.7,MIT
fs-extra,1.0.0,MIT
fs.realpath,1.0.0,ISC
fsevents,,unknown
-fstream,1.0.10,ISC
+fstream,1.0.11,ISC
fstream-ignore,1.0.5,ISC
function-bind,1.1.0,MIT
-gauge,2.7.2,ISC
+gauge,2.7.3,ISC
gemnasium-gitlab-service,0.2.6,MIT
gemojione,3.0.1,MIT
generate-function,2.0.0,MIT
@@ -377,7 +421,7 @@ generate-object-property,1.2.0,MIT
get-caller-file,1.0.2,ISC
get_process_mem,0.2.0,MIT
getpass,0.1.6,MIT
-gitaly,0.2.1,MIT
+gitaly,0.5.0,MIT
github-linguist,4.7.6,MIT
github-markup,1.4.0,MIT
gitlab-flowdock-git-hook,1.0.1,MIT
@@ -388,15 +432,16 @@ glob,7.1.1,ISC
glob-base,0.3.0,MIT
glob-parent,2.0.0,ISC
globalid,0.3.7,MIT
-globals,9.14.0,MIT
+globals,9.17.0,MIT
globby,5.0.0,MIT
gollum-grit_adapter,1.0.1,MIT
gollum-lib,4.2.1,MIT
-gollum-rugged_adapter,0.4.2,MIT
+gollum-rugged_adapter,0.4.4,MIT
gon,6.1.0,MIT
google-api-client,0.8.7,Apache 2.0
-google-protobuf,3.2.0,New BSD
+google-protobuf,3.2.0.2,New BSD
googleauth,0.5.1,Apache 2.0
+got,3.3.1,MIT
graceful-fs,4.1.11,ISC
graceful-readlink,1.0.1,MIT
grape,0.19.1,MIT
@@ -406,34 +451,40 @@ gzip-size,3.0.0,MIT
hamlit,2.6.1,MIT
handle-thing,1.2.5,MIT
handlebars,4.0.6,MIT
-har-validator,2.0.6,ISC
+har-schema,1.0.5,ISC
+har-validator,4.2.1,ISC
has,1.0.1,MIT
has-ansi,2.0.0,MIT
has-binary,0.1.7,MIT
has-cors,1.1.0,MIT
has-flag,1.0.0,MIT
has-unicode,2.0.1,ISC
+hash-sum,1.0.2,MIT
hash.js,1.0.3,MIT
hasha,2.2.0,MIT
hashie,3.5.5,MIT
hawk,3.1.3,New BSD
+he,1.1.1,MIT
health_check,2.6.0,MIT
hipchat,1.5.2,MIT
+hmac-drbg,1.0.0,MIT
hoek,2.16.3,New BSD
home-or-tmp,2.0.0,MIT
-hosted-git-info,2.2.0,ISC
+hosted-git-info,2.4.1,ISC
hpack.js,2.1.6,MIT
+html-comment-regex,1.1.1,MIT
html-entities,1.2.0,MIT
html-pipeline,1.11.0,MIT
html2text,0.2.0,MIT
htmlentities,4.3.4,MIT
+htmlparser2,3.9.2,MIT
http,0.9.8,MIT
http-cookie,1.0.3,MIT
http-deceiver,1.2.7,MIT
-http-errors,1.5.1,MIT
+http-errors,1.6.1,MIT
http-form_data,1.0.1,MIT
http-proxy,1.16.2,MIT
-http-proxy-middleware,0.17.3,MIT
+http-proxy-middleware,0.17.4,MIT
http-signature,1.1.1,MIT
http_parser.rb,0.6.0,MIT
httparty,0.13.7,MIT
@@ -442,24 +493,30 @@ https-browserify,0.0.1,MIT
i18n,0.8.1,MIT
ice_nine,0.11.2,MIT
iconv-lite,0.4.15,MIT
+icss-replace-symbols,1.0.2,ISC
ieee754,1.1.8,New BSD
-ignore,3.2.2,MIT
+ignore,3.2.6,MIT
+ignore-by-default,1.0.1,ISC
+immediate,3.0.6,MIT
imurmurhash,0.1.4,MIT
+indexes-of,1.0.1,MIT
indexof,0.0.1,unknown
+infinity-agent,2.0.3,MIT
inflight,1.0.6,ISC
influxdb,0.2.3,MIT
inherits,2.0.3,ISC
ini,1.3.4,ISC
inquirer,0.12.0,MIT
-interpret,1.0.1,MIT
+interpret,1.0.2,MIT
invariant,2.2.2,New BSD
invert-kv,1.0.0,MIT
-ipaddr.js,1.2.0,MIT
+ipaddr.js,1.3.0,MIT
ipaddress,0.8.3,MIT
is-absolute,0.2.6,MIT
+is-absolute-url,2.1.0,MIT
is-arrayish,0.2.1,MIT
is-binary-path,1.0.1,MIT
-is-buffer,1.1.4,MIT
+is-buffer,1.1.5,MIT
is-builtin-module,1.0.0,MIT
is-dotfile,1.0.2,MIT
is-equal-shallow,0.1.3,MIT
@@ -468,46 +525,52 @@ is-extglob,1.0.0,MIT
is-finite,1.0.2,MIT
is-fullwidth-code-point,1.0.0,MIT
is-glob,2.0.1,MIT
-is-my-json-valid,2.15.0,MIT
+is-my-json-valid,2.16.0,MIT
+is-npm,1.0.0,MIT
is-number,2.1.0,MIT
is-path-cwd,1.0.0,MIT
is-path-in-cwd,1.0.0,MIT
is-path-inside,1.0.0,MIT
+is-plain-obj,1.1.0,MIT
is-posix-bracket,0.1.1,MIT
is-primitive,2.0.0,MIT
is-property,1.0.2,MIT
+is-redirect,1.0.0,MIT
is-relative,0.2.1,MIT
is-resolvable,1.0.0,MIT
is-stream,1.1.0,MIT
+is-svg,2.1.0,MIT
is-typedarray,1.0.0,MIT
is-unc-path,0.1.2,MIT
is-utf8,0.2.1,MIT
is-windows,0.2.0,MIT
isarray,1.0.0,MIT
isbinaryfile,3.0.2,MIT
-isexe,1.1.2,ISC
+isexe,2.0.0,ISC
isobject,2.1.0,MIT
isstream,0.1.2,MIT
istanbul,0.4.5,New BSD
-istanbul-api,1.1.1,New BSD
-istanbul-lib-coverage,1.0.1,New BSD
-istanbul-lib-hook,1.0.0,New BSD
-istanbul-lib-instrument,1.4.2,New BSD
-istanbul-lib-report,1.0.0-alpha.3,New BSD
-istanbul-lib-source-maps,1.1.0,New BSD
-istanbul-reports,1.0.1,New BSD
+istanbul-api,1.1.7,New BSD
+istanbul-lib-coverage,1.0.2,New BSD
+istanbul-lib-hook,1.0.5,New BSD
+istanbul-lib-instrument,1.7.0,New BSD
+istanbul-lib-report,1.0.0,New BSD
+istanbul-lib-source-maps,1.1.1,New BSD
+istanbul-reports,1.0.2,New BSD
jasmine-core,2.5.2,MIT
jasmine-jquery,2.1.1,MIT
jira-ruby,1.1.2,MIT
jodid25519,1.0.2,MIT
-jquery,2.2.1,MIT
+jquery,2.2.4,MIT
jquery-atwho-rails,1.3.2,MIT
jquery-rails,4.1.1,MIT
-jquery-ujs,1.2.1,MIT
-js-cookie,2.1.3,MIT
+jquery-ujs,1.2.2,MIT
+js-base64,2.1.9,BSD
+js-beautify,1.6.12,MIT
+js-cookie,2.1.4,MIT
js-tokens,3.0.1,MIT
-js-yaml,3.8.1,MIT
-jsbn,0.1.0,BSD
+js-yaml,3.7.0,MIT
+jsbn,0.1.1,MIT
jsesc,1.3.0,MIT
json,1.8.6,ruby
json-jwt,1.7.1,MIT
@@ -520,51 +583,72 @@ json5,0.5.1,MIT
jsonfile,2.4.0,MIT
jsonify,0.0.0,Public Domain
jsonpointer,4.0.1,MIT
-jsprim,1.3.1,MIT
+jsprim,1.4.0,MIT
+jszip,3.1.3,(MIT OR GPL-3.0)
+jszip-utils,0.0.2,MIT or GPLv3
jwt,1.5.6,MIT
kaminari,0.17.0,MIT
-karma,1.4.1,MIT
-karma-coverage-istanbul-reporter,0.2.0,MIT
+karma,1.6.0,MIT
+karma-coverage-istanbul-reporter,0.2.3,MIT
karma-jasmine,1.1.0,MIT
-karma-mocha-reporter,2.2.2,MIT
-karma-phantomjs-launcher,1.0.2,MIT
+karma-mocha-reporter,2.2.3,MIT
+karma-phantomjs-launcher,1.0.4,MIT
karma-sourcemap-loader,0.3.7,MIT
-karma-webpack,2.0.2,MIT
+karma-webpack,2.0.3,MIT
kew,0.7.0,Apache 2.0
kgio,2.10.0,LGPL-2.1+
kind-of,3.1.0,MIT
klaw,1.3.1,MIT
kubeclient,2.2.0,MIT
+latest-version,1.0.1,MIT
launchy,2.4.3,ISC
lazy-cache,1.0.4,MIT
lcid,1.0.0,MIT
levn,0.3.0,MIT
licensee,8.7.0,MIT
+lie,3.1.1,MIT
little-plugger,1.1.4,MIT
load-json-file,1.1.0,MIT
loader-runner,2.3.0,MIT
-loader-utils,0.2.16,MIT
+loader-utils,0.2.17,MIT
locate-path,2.0.0,MIT
lodash,4.17.4,MIT
+lodash._baseassign,3.2.0,MIT
+lodash._basecopy,3.0.1,MIT
lodash._baseget,3.7.2,MIT
+lodash._bindcallback,3.0.1,MIT
+lodash._createassigner,3.1.1,MIT
+lodash._getnative,3.9.1,MIT
+lodash._isiterateecall,3.0.9,MIT
lodash._topath,3.8.1,MIT
-lodash.camelcase,4.1.1,MIT
+lodash.assign,3.2.0,MIT
+lodash.camelcase,4.3.0,MIT
lodash.capitalize,4.2.1,MIT
lodash.cond,4.5.2,MIT
lodash.deburr,4.1.0,MIT
-lodash.get,3.7.0,MIT
+lodash.defaults,3.1.2,MIT
+lodash.get,4.4.2,MIT
+lodash.isarguments,3.1.0,MIT
lodash.isarray,3.0.4,MIT
lodash.kebabcase,4.0.1,MIT
+lodash.keys,3.1.2,MIT
+lodash.memoize,4.1.2,MIT
+lodash.restparam,3.6.1,MIT
lodash.snakecase,4.0.1,MIT
+lodash.uniq,4.5.0,MIT
lodash.words,4.2.0,MIT
log4js,0.6.38,Apache 2.0
logging,2.1.0,MIT
longest,1.0.1,MIT
loofah,2.0.3,MIT
loose-envify,1.3.1,MIT
-lru-cache,2.2.4,MIT
+lowercase-keys,1.0.0,MIT
+lru-cache,3.2.0,ISC
+macaddress,0.2.8,MIT
mail,2.6.4,MIT
mail_room,0.9.1,MIT
+map-stream,0.1.0,unknown
+math-expression-evaluator,1.2.16,MIT
media-typer,0.3.0,MIT
memoist,0.15.0,MIT
memory-fs,0.4.1,MIT
@@ -574,16 +658,17 @@ methods,1.1.2,MIT
micromatch,2.3.11,MIT
miller-rabin,4.0.0,MIT
mime,1.3.4,MIT
-mime-db,1.26.0,MIT
+mime-db,1.27.0,MIT
mime-types,2.99.3,"MIT,Artistic-2.0,GPL-2.0"
mimemagic,0.3.0,MIT
mini_portile2,2.1.0,MIT
minimalistic-assert,1.0.0,ISC
+minimalistic-crypto-utils,1.0.1,MIT
minimatch,3.0.3,ISC
minimist,0.0.8,MIT
mkdirp,0.5.1,MIT
-moment,2.17.1,MIT
-mousetrap,1.4.6,Apache 2.0
+moment,2.18.1,MIT
+mousetrap,1.6.1,Apache 2.0
mousetrap-rails,1.4.6,"MIT,Apache"
ms,0.7.2,MIT
multi_json,1.12.1,MIT
@@ -595,17 +680,22 @@ mute-stream,0.0.5,ISC
nan,2.5.1,MIT
natural-compare,1.4.0,MIT
negotiator,0.6.1,MIT
+nested-error-stacks,1.0.2,MIT
net-ldap,0.12.1,MIT
net-ssh,3.0.1,MIT
netrc,0.11.0,MIT
node-libs-browser,2.0.0,MIT
-node-pre-gyp,0.6.33,New BSD
+node-pre-gyp,0.6.34,New BSD
node-zopfli,2.0.2,MIT
+nodemon,1.11.0,MIT
nokogiri,1.6.8.1,MIT
-nopt,3.0.6,ISC
-normalize-package-data,2.3.5,Simplified BSD
-normalize-path,2.0.1,MIT
+nopt,4.0.1,ISC
+normalize-package-data,2.3.6,Simplified BSD
+normalize-path,2.1.1,MIT
+normalize-range,0.1.2,MIT
+normalize-url,1.9.1,MIT
npmlog,4.0.2,ISC
+num2fraction,1.2.2,MIT
number-is-nan,1.0.1,MIT
numerizer,0.1.1,MIT
oauth,0.5.1,MIT
@@ -637,7 +727,7 @@ omniauth-twitter,1.2.1,MIT
omniauth_crowd,2.2.3,MIT
on-finished,2.3.0,MIT
on-headers,1.0.1,MIT
-once,1.3.3,ISC
+once,1.4.0,ISC
onetime,1.1.0,MIT
opener,1.4.3,(WTFPL OR MIT)
opn,4.0.2,MIT
@@ -652,11 +742,13 @@ os-browserify,0.2.1,MIT
os-homedir,1.0.2,MIT
os-locale,1.4.0,MIT
os-tmpdir,1.0.2,MIT
+osenv,0.1.4,ISC
p-limit,1.1.0,MIT
p-locate,2.0.0,MIT
-pako,0.2.9,MIT
+package-json,1.2.0,MIT
+pako,1.0.5,(MIT AND Zlib)
paranoia,2.2.0,MIT
-parse-asn1,5.0.0,ISC
+parse-asn1,5.1.0,ISC
parse-glob,3.0.4,MIT
parse-json,2.2.0,MIT
parsejson,0.0.3,MIT
@@ -670,8 +762,10 @@ path-is-inside,1.0.2,(WTFPL OR MIT)
path-parse,1.0.5,MIT
path-to-regexp,0.1.7,MIT
path-type,1.1.0,MIT
+pause-stream,0.0.11,"Apache2,MIT"
pbkdf2,3.0.9,MIT
pend,1.2.0,MIT
+performance-now,0.2.0,MIT
pg,0.18.4,"BSD,ruby,GPL"
phantomjs-prebuilt,2.1.14,Apache 2.0
pify,2.3.0,MIT
@@ -683,21 +777,63 @@ pkg-up,1.0.0,MIT
pluralize,1.2.1,MIT
portfinder,1.0.13,MIT
posix-spawn,0.3.11,"MIT,LGPL"
+postcss,5.2.16,MIT
+postcss-calc,5.3.1,MIT
+postcss-colormin,2.2.2,MIT
+postcss-convert-values,2.6.1,MIT
+postcss-discard-comments,2.0.4,MIT
+postcss-discard-duplicates,2.1.0,MIT
+postcss-discard-empty,2.1.0,MIT
+postcss-discard-overridden,0.1.1,MIT
+postcss-discard-unused,2.2.3,MIT
+postcss-filter-plugins,2.0.2,MIT
+postcss-load-config,1.2.0,MIT
+postcss-load-options,1.2.0,MIT
+postcss-load-plugins,2.3.0,MIT
+postcss-merge-idents,2.1.7,MIT
+postcss-merge-longhand,2.0.2,MIT
+postcss-merge-rules,2.1.2,MIT
+postcss-message-helpers,2.0.0,MIT
+postcss-minify-font-values,1.0.5,MIT
+postcss-minify-gradients,1.0.5,MIT
+postcss-minify-params,1.2.2,MIT
+postcss-minify-selectors,2.1.1,MIT
+postcss-modules-extract-imports,1.0.1,ISC
+postcss-modules-local-by-default,1.1.1,MIT
+postcss-modules-scope,1.0.2,ISC
+postcss-modules-values,1.2.2,ISC
+postcss-normalize-charset,1.1.1,MIT
+postcss-normalize-url,3.0.8,MIT
+postcss-ordered-values,2.2.3,MIT
+postcss-reduce-idents,2.4.0,MIT
+postcss-reduce-initial,1.0.1,MIT
+postcss-reduce-transforms,1.0.4,MIT
+postcss-selector-parser,2.2.3,MIT
+postcss-svgo,2.1.6,MIT
+postcss-unique-selectors,2.0.2,MIT
+postcss-value-parser,3.3.0,MIT
+postcss-zindex,2.2.0,MIT
prelude-ls,1.1.2,MIT
premailer,1.8.6,New BSD
premailer-rails,1.9.2,MIT
+prepend-http,1.0.4,MIT
preserve,0.2.0,MIT
private,0.1.7,MIT
process,0.11.9,MIT
process-nextick-args,1.0.7,MIT
progress,1.1.8,MIT
-proxy-addr,1.1.3,MIT
+proto-list,1.2.4,ISC
+proxy-addr,1.1.4,MIT
prr,0.0.0,MIT
+ps-tree,1.1.0,MIT
+pseudomap,1.0.2,ISC
public-encrypt,4.0.0,MIT
punycode,1.4.1,MIT
pyu-ruby-sasl,0.0.3.3,MIT
+q,1.5.0,MIT
qjobs,1.1.5,MIT
-qs,6.2.0,New BSD
+qs,6.4.0,New BSD
+query-string,4.3.2,MIT
querystring,0.2.0,MIT
querystring-es3,0.2.1,MIT
querystringify,0.0.4,MIT
@@ -723,16 +859,19 @@ range-parser,1.2.0,MIT
raphael,2.2.7,MIT
raw-body,2.2.0,MIT
raw-loader,0.5.1,MIT
-rc,1.1.6,(BSD-2-Clause OR MIT OR Apache-2.0)
+rc,1.2.1,(BSD-2-Clause OR MIT OR Apache-2.0)
rdoc,4.2.2,ruby
+react-dev-utils,0.5.2,New BSD
+read-all-stream,3.1.0,MIT
read-pkg,1.1.0,MIT
read-pkg-up,1.0.1,MIT
-readable-stream,2.1.5,MIT
+readable-stream,2.0.6,MIT
readdirp,2.1.0,MIT
readline2,1.0.1,MIT
recaptcha,3.0.0,MIT
rechoir,0.6.2,MIT
recursive-open-struct,1.0.0,MIT
+recursive-readdir,2.1.1,MIT
redcarpet,3.4.0,MIT
redis,3.2.2,MIT
redis-actionpack,5.0.1,MIT
@@ -741,31 +880,36 @@ redis-namespace,1.5.2,MIT
redis-rack,1.6.0,MIT
redis-rails,5.0.1,MIT
redis-store,1.2.0,MIT
+reduce-css-calc,1.3.0,MIT
+reduce-function-call,1.0.2,MIT
regenerate,1.3.2,MIT
-regenerator-runtime,0.10.1,MIT
+regenerator-runtime,0.10.3,MIT
regenerator-transform,0.9.8,BSD
regex-cache,0.4.3,MIT
regexpu-core,2.0.0,MIT
+registry-url,3.1.0,MIT
regjsgen,0.2.0,MIT
regjsparser,0.1.5,BSD
+remove-trailing-separator,1.0.1,ISC
repeat-element,1.1.2,MIT
repeat-string,1.6.1,MIT
repeating,2.0.1,MIT
-request,2.79.0,Apache 2.0
+request,2.81.0,Apache 2.0
request-progress,2.0.1,MIT
request_store,1.3.1,MIT
require-directory,2.1.1,MIT
+require-from-string,1.2.1,MIT
require-main-filename,1.0.1,ISC
require-uncached,1.0.3,MIT
requires-port,1.0.0,MIT
-resolve,1.2.0,MIT
+resolve,1.3.2,MIT
resolve-from,1.0.1,MIT
responders,2.3.0,MIT
rest-client,2.0.0,MIT
restore-cursor,1.0.1,MIT
retriable,1.4.1,MIT
right-align,0.1.3,MIT
-rimraf,2.5.4,ISC
+rimraf,2.6.1,ISC
rinku,2.0.0,ISC
ripemd160,1.0.1,New BSD
rotp,2.1.2,MIT
@@ -778,7 +922,7 @@ ruby-saml,1.4.1,MIT
rubyntlm,0.5.2,MIT
rubypants,0.2.0,BSD
rufus-scheduler,3.1.10,MIT
-rugged,0.24.0,MIT
+rugged,0.25.1.1,MIT
run-async,0.1.0,MIT
rx-lite,3.1.2,Apache 2.0
safe-buffer,5.0.1,MIT
@@ -787,158 +931,190 @@ sanitize,2.1.0,MIT
sass,3.4.22,MIT
sass-rails,5.0.6,MIT
sawyer,0.8.1,MIT
+sax,1.2.2,ISC
securecompare,1.0.0,MIT
seed-fu,2.3.6,MIT
select-hose,2.0.0,MIT
select2,3.5.2-browserify,unknown
select2-rails,3.5.9.3,MIT
semver,5.3.0,ISC
-send,0.14.2,MIT
-sentry-raven,2.0.2,Apache 2.0
+semver-diff,2.1.0,MIT
+send,0.15.1,MIT
+sentry-raven,2.4.0,Apache 2.0
serve-index,1.8.0,MIT
-serve-static,1.11.2,MIT
+serve-static,1.12.1,MIT
set-blocking,2.0.0,ISC
set-immediate-shim,1.0.1,MIT
setimmediate,1.0.5,MIT
-setprototypeof,1.0.2,ISC
+setprototypeof,1.0.3,ISC
settingslogic,2.0.9,MIT
sha.js,2.4.8,MIT
-shelljs,0.7.6,New BSD
+shelljs,0.7.7,New BSD
sidekiq,4.2.7,LGPL
sidekiq-cron,0.4.4,MIT
sidekiq-limit_fetch,3.4.0,MIT
+sigmund,1.0.1,ISC
signal-exit,3.0.2,ISC
signet,0.7.3,Apache 2.0
slack-notifier,1.5.1,MIT
slash,1.0.0,MIT
slice-ansi,0.0.4,MIT
+slide,1.1.6,ISC
sntp,1.0.9,BSD
-socket.io,1.7.2,MIT
+socket.io,1.7.3,MIT
socket.io-adapter,0.5.0,MIT
-socket.io-client,1.7.2,MIT
+socket.io-client,1.7.3,MIT
socket.io-parser,2.3.1,MIT
sockjs,0.3.18,MIT
-sockjs-client,1.1.1,MIT
+sockjs-client,1.0.1,MIT
+sort-keys,1.1.2,MIT
source-list-map,0.1.8,MIT
source-map,0.5.6,New BSD
-source-map-support,0.4.11,MIT
+source-map-support,0.4.14,MIT
spdx-correct,1.0.2,Apache 2.0
spdx-expression-parse,1.0.4,(MIT AND CC-BY-3.0)
spdx-license-ids,1.2.2,Unlicense
spdy,3.4.4,MIT
spdy-transport,2.0.18,MIT
+split,0.3.3,MIT
sprintf-js,1.0.3,New BSD
sprockets,3.7.1,MIT
sprockets-rails,3.2.0,MIT
-sshpk,1.10.2,MIT
+sshpk,1.11.0,MIT
state_machines,0.4.0,MIT
state_machines-activemodel,0.4.0,MIT
state_machines-activerecord,0.4.0,MIT
stats-webpack-plugin,0.4.3,MIT
statuses,1.3.1,MIT
stream-browserify,2.0.1,MIT
-stream-http,2.6.3,MIT
+stream-combiner,0.0.4,MIT
+stream-http,2.7.0,MIT
+stream-shift,1.0.0,MIT
+strict-uri-encode,1.1.0,MIT
+string-length,1.0.1,MIT
string-width,1.0.2,MIT
-string.fromcodepoint,0.2.1,MIT
-string.prototype.codepointat,0.2.0,MIT
string_decoder,0.10.31,MIT
stringex,2.5.2,MIT
stringstream,0.0.5,MIT
strip-ansi,3.0.1,MIT
strip-bom,2.0.0,MIT
-strip-json-comments,1.0.4,MIT
-supports-color,0.2.0,MIT
+strip-json-comments,2.0.1,MIT
+supports-color,3.2.3,MIT
+svgo,0.7.2,MIT
sys-filesystem,1.1.6,Artistic 2.0
table,3.8.3,New BSD
tapable,0.2.6,MIT
tar,2.2.1,ISC
-tar-pack,3.3.0,Simplified BSD
+tar-pack,3.4.0,Simplified BSD
temple,0.7.7,MIT
-test-exclude,4.0.0,ISC
+test-exclude,4.0.3,ISC
text-table,0.2.0,MIT
thor,0.19.4,MIT
thread_safe,0.3.6,Apache 2.0
+three,0.84.0,MIT
+three-orbit-controls,82.1.0,MIT
+three-stl-loader,1.0.4,MIT
throttleit,1.0.0,MIT
through,2.3.8,MIT
tilt,2.0.6,MIT
timeago.js,2.0.5,MIT
+timed-out,2.0.0,MIT
timers-browserify,2.0.2,MIT
timfel-krb5-auth,0.8.3,LGPL
-tmp,0.0.28,MIT
+tmp,0.0.31,MIT
to-array,0.1.4,MIT
to-arraybuffer,1.0.1,MIT
to-fast-properties,1.0.2,MIT
+toml-rb,0.3.15,MIT
tool,0.2.3,MIT
+touch,1.0.0,ISC
tough-cookie,2.3.2,New BSD
+traverse,0.6.6,MIT
trim-right,1.0.1,MIT
truncato,0.7.8,MIT
tryit,1.0.3,MIT
tty-browserify,0.0.0,MIT
-tunnel-agent,0.4.3,Apache 2.0
+tunnel-agent,0.6.0,Apache 2.0
tweetnacl,0.14.5,Unlicense
type-check,0.3.2,MIT
-type-is,1.6.14,MIT
+type-is,1.6.15,MIT
typedarray,0.0.6,MIT
tzinfo,1.2.2,MIT
u2f,0.2.1,MIT
uglifier,2.7.2,MIT
-uglify-js,2.7.5,Simplified BSD
+uglify-js,2.8.21,Simplified BSD
uglify-to-browserify,1.0.2,MIT
uid-number,0.0.6,ISC
ultron,1.0.2,MIT
unc-path-regex,0.1.2,MIT
+undefsafe,0.0.3,MIT / http://rem.mit-license.org
underscore,1.8.3,MIT
underscore-rails,1.8.3,MIT
unf,0.1.4,BSD
unf_ext,0.0.7.2,MIT
unicorn,5.1.0,ruby
unicorn-worker-killer,0.4.4,ruby
+uniq,1.0.1,MIT
+uniqid,4.1.1,MIT
+uniqs,2.0.0,MIT
unpipe,1.0.0,MIT
+update-notifier,0.5.0,Simplified BSD
url,0.11.0,MIT
url-parse,1.0.5,MIT
url_safe_base64,0.2.2,MIT
user-home,2.0.0,MIT
-useragent,2.1.12,MIT
+useragent,2.1.13,MIT
util,0.10.3,MIT
util-deprecate,1.0.2,MIT
utils-merge,1.0.0,MIT
uuid,3.0.1,MIT
validate-npm-package-license,3.0.1,Apache 2.0
validates_hostname,1.0.6,MIT
-vary,1.1.0,MIT
+vary,1.1.1,MIT
+vendors,1.0.1,MIT
verror,1.3.6,MIT
version_sorter,2.1.0,MIT
virtus,1.0.5,MIT
+visibilityjs,1.2.4,MIT
vm-browserify,0.0.4,MIT
vmstat,2.3.0,MIT
void-elements,2.0.1,MIT
-vue,2.1.10,MIT
+vue,2.2.6,MIT
+vue-hot-reload-api,2.0.11,MIT
+vue-loader,11.3.4,MIT
vue-resource,0.9.3,MIT
+vue-style-loader,2.0.5,MIT
+vue-template-compiler,2.2.6,MIT
+vue-template-es2015-compiler,1.5.2,MIT
warden,1.2.6,MIT
-watchpack,1.2.1,MIT
+watchpack,1.3.1,MIT
wbuf,1.7.2,MIT
-webpack,2.2.1,MIT
-webpack-bundle-analyzer,2.3.0,MIT
-webpack-dev-middleware,1.10.0,MIT
-webpack-dev-server,2.3.0,MIT
-webpack-rails,0.9.9,MIT
-webpack-sources,0.1.4,MIT
+webpack,2.3.3,MIT
+webpack-bundle-analyzer,2.3.1,MIT
+webpack-dev-middleware,1.10.1,MIT
+webpack-dev-server,2.4.2,MIT
+webpack-rails,0.9.10,MIT
+webpack-sources,0.1.5,MIT
websocket-driver,0.6.5,MIT
websocket-extensions,0.1.1,MIT
-which,1.2.12,ISC
+whet.extend,0.9.9,MIT
+which,1.2.14,ISC
which-module,1.0.0,ISC
wide-align,1.1.0,ISC
wikicloth,0.8.1,MIT
window-size,0.1.0,MIT
-wordwrap,0.0.2,MIT/X11
+wordwrap,1.0.0,MIT
wrap-ansi,2.1.0,MIT
wrappy,1.0.2,ISC
write,0.2.1,MIT
-ws,1.1.1,MIT
+write-file-atomic,1.3.1,ISC
+ws,1.1.2,MIT
wtf-8,1.0.0,MIT
+xdg-basedir,2.0.0,MIT
xmlhttprequest-ssl,1.5.3,MIT
xtend,4.0.1,MIT
y18n,3.2.1,ISC
+yallist,2.1.2,ISC
yargs,3.10.0,MIT
yargs-parser,4.2.1,ISC
yauzl,2.4.1,MIT
diff --git a/yarn.lock b/yarn.lock
index 2434b3a8a48..e16cd9c3673 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -60,6 +60,10 @@ align-text@^0.1.1, align-text@^0.1.3:
longest "^1.0.1"
repeat-string "^1.5.2"
+alphanum-sort@^1.0.1, alphanum-sort@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
+
amdefine@>=0.0.4:
version "1.0.1"
resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
@@ -68,6 +72,10 @@ 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.5:
+ version "0.0.5"
+ resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.5.tgz#0dcaa5a081206866bc240a3b773a184ea3b88b64"
+
ansi-html@0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e"
@@ -206,6 +214,17 @@ asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
+autoprefixer@^6.3.1:
+ version "6.7.7"
+ resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.7.7.tgz#1dbd1c835658e35ce3f9984099db00585c782014"
+ dependencies:
+ browserslist "^1.7.6"
+ caniuse-db "^1.0.30000634"
+ normalize-range "^0.1.2"
+ num2fraction "^1.2.2"
+ postcss "^5.2.16"
+ postcss-value-parser "^3.2.3"
+
aws-sign2@~0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f"
@@ -214,7 +233,7 @@ 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:
+babel-code-frame@^6.11.0, 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:
@@ -805,7 +824,7 @@ 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:
+balanced-match@^0.4.1, balanced-match@^0.4.2:
version "0.4.2"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838"
@@ -855,7 +874,7 @@ block-stream@*:
dependencies:
inherits "~2.0.0"
-bluebird@^3.3.0:
+bluebird@^3.0.5, bluebird@^3.1.1, bluebird@^3.3.0:
version "3.4.7"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3"
@@ -964,6 +983,13 @@ browserify-zlib@^0.1.4:
dependencies:
pako "~0.2.0"
+browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6:
+ version "1.7.7"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-1.7.7.tgz#0bd76704258be829b2398bb50e4b62d1a166b0b9"
+ dependencies:
+ caniuse-db "^1.0.30000639"
+ electron-to-chromium "^1.2.7"
+
buffer-shims@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51"
@@ -1018,6 +1044,19 @@ camelcase@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a"
+caniuse-api@^1.5.2:
+ version "1.6.1"
+ resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-1.6.1.tgz#b534e7c734c4f81ec5fbe8aca2ad24354b962c6c"
+ dependencies:
+ browserslist "^1.3.6"
+ caniuse-db "^1.0.30000529"
+ lodash.memoize "^4.1.2"
+ lodash.uniq "^4.5.0"
+
+caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
+ version "1.0.30000649"
+ resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000649.tgz#1ee1754a6df235450c8b7cd15e0ebf507221a86a"
+
caseless@~0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7"
@@ -1064,6 +1103,12 @@ circular-json@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d"
+clap@^1.0.9:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/clap/-/clap-1.1.3.tgz#b3bd36e93dd4cbfb395a3c26896352445265c05b"
+ dependencies:
+ chalk "^1.1.3"
+
cli-cursor@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987"
@@ -1098,11 +1143,49 @@ co@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
+coa@~1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/coa/-/coa-1.0.1.tgz#7f959346cfc8719e3f7233cd6852854a7c67d8a3"
+ dependencies:
+ q "^1.1.2"
+
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:
+color-convert@^1.3.0:
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.0.tgz#1accf97dd739b983bf994d56fec8f95853641b7a"
+ dependencies:
+ color-name "^1.1.1"
+
+color-name@^1.0.0, color-name@^1.1.1:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.2.tgz#5c8ab72b64bd2215d617ae9559ebb148475cf98d"
+
+color-string@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/color-string/-/color-string-0.3.0.tgz#27d46fb67025c5c2fa25993bfbf579e47841b991"
+ dependencies:
+ color-name "^1.0.0"
+
+color@^0.11.0:
+ version "0.11.4"
+ resolved "https://registry.yarnpkg.com/color/-/color-0.11.4.tgz#6d7b5c74fb65e841cd48792ad1ed5e07b904d764"
+ dependencies:
+ clone "^1.0.2"
+ color-convert "^1.3.0"
+ color-string "^0.3.0"
+
+colormin@^1.0.5:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/colormin/-/colormin-1.1.2.tgz#ea2f7420a72b96881a38aae59ec124a6f7298133"
+ dependencies:
+ color "^0.11.0"
+ css-color-names "0.0.4"
+ has "^1.0.1"
+
+colors@^1.1.0, colors@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
@@ -1190,6 +1273,26 @@ concat-stream@^1.4.6:
readable-stream "^2.2.2"
typedarray "^0.0.6"
+config-chain@~1.1.5:
+ version "1.1.11"
+ resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.11.tgz#aba09747dfbe4c3e70e766a6e41586e1859fc6f2"
+ dependencies:
+ ini "^1.3.4"
+ proto-list "~1.2.1"
+
+configstore@^1.0.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/configstore/-/configstore-1.4.0.tgz#c35781d0501d268c25c54b8b17f6240e8a4fb021"
+ dependencies:
+ graceful-fs "^4.1.2"
+ mkdirp "^0.5.0"
+ object-assign "^4.0.1"
+ os-tmpdir "^1.0.0"
+ osenv "^0.1.0"
+ uuid "^2.0.1"
+ write-file-atomic "^1.1.2"
+ xdg-basedir "^2.0.0"
+
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"
@@ -1213,6 +1316,12 @@ 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"
+consolidate@^0.14.0:
+ version "0.14.5"
+ resolved "https://registry.yarnpkg.com/consolidate/-/consolidate-0.14.5.tgz#5a25047bc76f73072667c8cb52c989888f494c63"
+ dependencies:
+ bluebird "^3.1.1"
+
constants-browserify@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
@@ -1253,6 +1362,17 @@ 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"
+cosmiconfig@^2.1.0, cosmiconfig@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-2.1.1.tgz#817f2c2039347a1e9bf7d090c0923e53f749ca82"
+ dependencies:
+ js-yaml "^3.4.3"
+ minimist "^1.2.0"
+ object-assign "^4.1.0"
+ os-homedir "^1.0.1"
+ parse-json "^2.2.0"
+ require-from-string "^1.1.0"
+
create-ecdh@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.0.tgz#888c723596cdf7612f6498233eebd7a35301737d"
@@ -1297,6 +1417,91 @@ crypto-browserify@^3.11.0:
public-encrypt "^4.0.0"
randombytes "^2.0.0"
+css-color-names@0.0.4:
+ version "0.0.4"
+ resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
+
+css-loader@^0.28.0:
+ version "0.28.0"
+ resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.28.0.tgz#417cfa9789f8cde59a30ccbf3e4da7a806889bad"
+ dependencies:
+ babel-code-frame "^6.11.0"
+ css-selector-tokenizer "^0.7.0"
+ cssnano ">=2.6.1 <4"
+ loader-utils "^1.0.2"
+ lodash.camelcase "^4.3.0"
+ object-assign "^4.0.1"
+ postcss "^5.0.6"
+ postcss-modules-extract-imports "^1.0.0"
+ postcss-modules-local-by-default "^1.0.1"
+ postcss-modules-scope "^1.0.0"
+ postcss-modules-values "^1.1.0"
+ source-list-map "^0.1.7"
+
+css-selector-tokenizer@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.6.0.tgz#6445f582c7930d241dcc5007a43d6fcb8f073152"
+ dependencies:
+ cssesc "^0.1.0"
+ fastparse "^1.1.1"
+ regexpu-core "^1.0.0"
+
+css-selector-tokenizer@^0.7.0:
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz#e6988474ae8c953477bf5e7efecfceccd9cf4c86"
+ dependencies:
+ cssesc "^0.1.0"
+ fastparse "^1.1.1"
+ regexpu-core "^1.0.0"
+
+cssesc@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4"
+
+"cssnano@>=2.6.1 <4":
+ version "3.10.0"
+ resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-3.10.0.tgz#4f38f6cea2b9b17fa01490f23f1dc68ea65c1c38"
+ dependencies:
+ autoprefixer "^6.3.1"
+ decamelize "^1.1.2"
+ defined "^1.0.0"
+ has "^1.0.1"
+ object-assign "^4.0.1"
+ postcss "^5.0.14"
+ postcss-calc "^5.2.0"
+ postcss-colormin "^2.1.8"
+ postcss-convert-values "^2.3.4"
+ postcss-discard-comments "^2.0.4"
+ postcss-discard-duplicates "^2.0.1"
+ postcss-discard-empty "^2.0.1"
+ postcss-discard-overridden "^0.1.1"
+ postcss-discard-unused "^2.2.1"
+ postcss-filter-plugins "^2.0.0"
+ postcss-merge-idents "^2.1.5"
+ postcss-merge-longhand "^2.0.1"
+ postcss-merge-rules "^2.0.3"
+ postcss-minify-font-values "^1.0.2"
+ postcss-minify-gradients "^1.0.1"
+ postcss-minify-params "^1.0.4"
+ postcss-minify-selectors "^2.0.4"
+ postcss-normalize-charset "^1.1.0"
+ postcss-normalize-url "^3.0.7"
+ postcss-ordered-values "^2.1.0"
+ postcss-reduce-idents "^2.2.2"
+ postcss-reduce-initial "^1.0.0"
+ postcss-reduce-transforms "^1.0.3"
+ postcss-svgo "^2.1.1"
+ postcss-unique-selectors "^2.0.2"
+ postcss-value-parser "^3.2.3"
+ postcss-zindex "^2.0.1"
+
+csso@~2.3.1:
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/csso/-/csso-2.3.2.tgz#ddd52c587033f49e94b71fc55569f252e8ff5f85"
+ dependencies:
+ clap "^1.0.9"
+ source-map "^0.5.3"
+
custom-event@~1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425"
@@ -1321,6 +1526,10 @@ date-now@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
+de-indent@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
+
debug@0.7.4:
version "0.7.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-0.7.4.tgz#06e1ea8082c2cb14e39806e22e2f6f757f92af39"
@@ -1337,13 +1546,13 @@ debug@2.3.3:
dependencies:
ms "0.7.2"
-debug@2.6.0, debug@^2.1.1, debug@^2.2.0:
+debug@2.6.0, debug@^2.1.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:
+decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
@@ -1367,6 +1576,10 @@ defaults@^1.0.2:
dependencies:
clone "^1.0.2"
+defined@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
+
del@^2.0.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8"
@@ -1440,24 +1653,70 @@ dom-serialize@^2.2.0:
extend "^3.0.0"
void-elements "^2.0.0"
+dom-serializer@0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82"
+ dependencies:
+ domelementtype "~1.1.1"
+ entities "~1.1.1"
+
domain-browser@^1.1.1:
version "1.1.7"
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc"
+domelementtype@1, domelementtype@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2"
+
+domelementtype@~1.1.1:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b"
+
+domhandler@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.3.0.tgz#2de59a0822d5027fabff6f032c2b25a2a8abe738"
+ dependencies:
+ domelementtype "1"
+
+domutils@^1.5.1:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf"
+ dependencies:
+ dom-serializer "0"
+ domelementtype "1"
+
dropzone@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/dropzone/-/dropzone-4.2.0.tgz#fbe7acbb9918e0706489072ef663effeef8a79f3"
-duplexer@^0.1.1:
+duplexer@^0.1.1, duplexer@~0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1"
+duplexify@^3.2.0:
+ version "3.5.0"
+ resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.0.tgz#1aa773002e1578457e9d9d4a50b0ccaaebcbd604"
+ dependencies:
+ end-of-stream "1.0.0"
+ inherits "^2.0.1"
+ readable-stream "^2.0.0"
+ stream-shift "^1.0.0"
+
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"
+editorconfig@^0.13.2:
+ version "0.13.2"
+ resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.13.2.tgz#8e57926d9ee69ab6cb999f027c2171467acceb35"
+ dependencies:
+ bluebird "^3.0.5"
+ commander "^2.9.0"
+ lru-cache "^3.2.0"
+ sigmund "^1.0.1"
+
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@@ -1466,6 +1725,10 @@ ejs@^2.5.5:
version "2.5.6"
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.6.tgz#479636bfa3fe3b1debd52087f0acb204b4f19c88"
+electron-to-chromium@^1.2.7:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.3.tgz#651eb63fe89f39db70ffc8dbd5d9b66958bc6a0e"
+
elliptic@^6.0.0:
version "6.3.3"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.3.3.tgz#5482d9646d54bcb89fd7d994fc9e2e9568876e3f"
@@ -1487,6 +1750,12 @@ encodeurl@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20"
+end-of-stream@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.0.0.tgz#d4596e702734a93e40e9af864319eabd99ff2f0e"
+ dependencies:
+ once "~1.3.0"
+
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"
@@ -1547,6 +1816,10 @@ ent@~2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d"
+entities@^1.1.1, entities@~1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0"
+
errno@^0.1.3:
version "0.1.4"
resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.4.tgz#b896e23a9e5e8ba33871fc996abd3635fc9a1c7d"
@@ -1585,7 +1858,7 @@ es6-map@^0.1.3:
es6-symbol "~3.1.0"
event-emitter "~0.3.4"
-es6-promise@~3.0.2:
+es6-promise@^3.0.2, es6-promise@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.0.2.tgz#010d5858423a5f118979665f46486a95c6ee2bb6"
@@ -1623,7 +1896,7 @@ 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:
+escape-string-regexp@1.0.5, 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"
@@ -1690,6 +1963,12 @@ eslint-plugin-filenames@^1.1.0:
lodash.kebabcase "4.0.1"
lodash.snakecase "4.0.1"
+eslint-plugin-html@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-html/-/eslint-plugin-html-2.0.1.tgz#3a829510e82522f1e2e44d55d7661a176121fce1"
+ dependencies:
+ htmlparser2 "^3.8.2"
+
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"
@@ -1755,7 +2034,7 @@ espree@^3.4.0:
acorn "4.0.4"
acorn-jsx "^3.0.0"
-esprima@2.7.x, esprima@^2.7.1:
+esprima@2.7.x, esprima@^2.6.0, esprima@^2.7.1:
version "2.7.3"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581"
@@ -1801,6 +2080,18 @@ event-emitter@~0.3.4:
d "~0.1.1"
es5-ext "~0.10.7"
+event-stream@~3.3.0:
+ version "3.3.4"
+ resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571"
+ dependencies:
+ duplexer "~0.1.1"
+ from "~0"
+ map-stream "~0.1.0"
+ pause-stream "0.0.11"
+ split "0.3"
+ stream-combiner "~0.0.4"
+ through "~2.3.1"
+
eventemitter3@1.x.x:
version "1.2.0"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508"
@@ -1809,7 +2100,7 @@ events@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
-eventsource@0.1.6:
+eventsource@0.1.6, eventsource@^0.1.3:
version "0.1.6"
resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-0.1.6.tgz#0acede849ed7dd1ccc32c811bb11b944d4f29232"
dependencies:
@@ -1910,6 +2201,10 @@ fast-levenshtein@~2.0.4:
version "2.0.6"
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
+fastparse@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8"
+
faye-websocket@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4"
@@ -1922,6 +2217,12 @@ faye-websocket@~0.11.0:
dependencies:
websocket-driver ">=0.5.1"
+faye-websocket@~0.7.3:
+ version "0.7.3"
+ resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.7.3.tgz#cc4074c7f4a4dfd03af54dd65c354b135132ce11"
+ dependencies:
+ websocket-driver ">=0.3.6"
+
fd-slicer@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65"
@@ -1959,6 +2260,10 @@ fileset@^2.0.2:
glob "^7.0.3"
minimatch "^3.0.3"
+filesize@3.3.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.3.0.tgz#53149ea3460e3b2e024962a51648aa572cf98122"
+
filesize@^3.5.4:
version "3.5.4"
resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.5.4.tgz#742fc7fb6aef4ee3878682600c22f840731e1fda"
@@ -2027,6 +2332,10 @@ flat-cache@^1.2.1:
graceful-fs "^4.1.2"
write "^0.2.1"
+flatten@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782"
+
for-in@^0.1.5:
version "0.1.6"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.6.tgz#c9f96e89bfad18a545af5ec3ed352a1d9e5b4dc8"
@@ -2057,6 +2366,10 @@ fresh@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.3.0.tgz#651f838e22424e7566de161d8358caa199f83d4f"
+from@~0:
+ version "0.1.7"
+ resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe"
+
fs-extra@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-1.0.0.tgz#cd3ce5f7e7cb6145883fcae3191e9877f8587950"
@@ -2180,7 +2493,22 @@ globby@^5.0.0:
pify "^2.0.0"
pinkie-promise "^2.0.0"
-graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9:
+got@^3.2.0:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/got/-/got-3.3.1.tgz#e5d0ed4af55fc3eef4d56007769d98192bcb2eca"
+ dependencies:
+ duplexify "^3.2.0"
+ infinity-agent "^2.0.0"
+ is-redirect "^1.0.0"
+ is-stream "^1.0.0"
+ lowercase-keys "^1.0.0"
+ nested-error-stacks "^1.0.0"
+ object-assign "^3.0.0"
+ prepend-http "^1.0.0"
+ read-all-stream "^3.0.0"
+ timed-out "^2.0.0"
+
+graceful-fs@^4.1.11, 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"
@@ -2188,7 +2516,7 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9:
version "1.0.1"
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
-gzip-size@^3.0.0:
+gzip-size@3.0.0, gzip-size@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-3.0.0.tgz#546188e9bdc337f673772f81660464b389dce520"
dependencies:
@@ -2247,6 +2575,10 @@ has@^1.0.1:
dependencies:
function-bind "^1.0.2"
+hash-sum@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-1.0.2.tgz#33b40777754c6432573c120cc3808bbd10d47f04"
+
hash.js@^1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.0.3.tgz#1332ff00156c0a0ffdd8236013d07b77a0451573"
@@ -2269,6 +2601,10 @@ hawk@~3.1.3:
hoek "2.x.x"
sntp "1.x.x"
+he@^1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
+
hoek@2.x.x:
version "2.16.3"
resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
@@ -2293,10 +2629,25 @@ hpack.js@^2.1.6:
readable-stream "^2.0.1"
wbuf "^1.1.0"
-html-entities@^1.2.0:
+html-comment-regex@^1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e"
+
+html-entities@1.2.0, html-entities@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.0.tgz#41948caf85ce82fed36e4e6a0ed371a6664379e2"
+htmlparser2@^3.8.2:
+ version "3.9.2"
+ resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338"
+ dependencies:
+ domelementtype "^1.3.0"
+ domhandler "^2.3.0"
+ domutils "^1.5.1"
+ entities "^1.1.1"
+ inherits "^2.0.1"
+ readable-stream "^2.0.2"
+
http-deceiver@^1.2.4:
version "1.2.7"
resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
@@ -2341,10 +2692,18 @@ iconv-lite@0.4.15:
version "0.4.15"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb"
+icss-replace-symbols@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.0.2.tgz#cb0b6054eb3af6edc9ab1d62d01933e2d4c8bfa5"
+
ieee754@^1.1.4:
version "1.1.8"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"
+ignore-by-default@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09"
+
ignore@^3.2.0:
version "3.2.2"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.2.2.tgz#1c51e1ef53bab6ddc15db4d9ac4ec139eceb3410"
@@ -2357,10 +2716,18 @@ imurmurhash@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
+indexes-of@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"
+
indexof@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d"
+infinity-agent@^2.0.0:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/infinity-agent/-/infinity-agent-2.0.3.tgz#45e0e2ff7a9eb030b27d62b74b3744b7a7ac4216"
+
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
@@ -2376,7 +2743,7 @@ inherits@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
-ini@~1.3.0:
+ini@^1.3.4, ini@~1.3.0:
version "1.3.4"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e"
@@ -2416,6 +2783,10 @@ 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-url@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6"
+
is-absolute@^0.2.3:
version "0.2.6"
resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-0.2.6.tgz#20de69f3db942ef2d87b9c2da36f172235b1b5eb"
@@ -2502,6 +2873,10 @@ is-my-json-valid@^2.10.0, is-my-json-valid@^2.12.4:
jsonpointer "^4.0.0"
xtend "^4.0.0"
+is-npm@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4"
+
is-number@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-0.1.1.tgz#69a7af116963d47206ec9bd9b48a14216f1e3806"
@@ -2528,6 +2903,10 @@ is-path-inside@^1.0.0:
dependencies:
path-is-inside "^1.0.1"
+is-plain-obj@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
+
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"
@@ -2540,6 +2919,10 @@ is-property@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84"
+is-redirect@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24"
+
is-relative@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-0.2.1.tgz#d27f4c7d516d175fb610db84bbeef23c3bc97aa5"
@@ -2552,10 +2935,16 @@ is-resolvable@^1.0.0:
dependencies:
tryit "^1.0.1"
-is-stream@^1.0.1:
+is-stream@^1.0.0, is-stream@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
+is-svg@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-2.1.0.tgz#cf61090da0d9efbcab8722deba6f032208dbb0e9"
+ dependencies:
+ html-comment-regex "^1.1.0"
+
is-typedarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
@@ -2707,6 +3096,19 @@ 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-base64@^2.1.9:
+ version "2.1.9"
+ resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.1.9.tgz#f0e80ae039a4bd654b5f281fc93f04a914a7fcce"
+
+js-beautify@^1.6.3:
+ version "1.6.12"
+ resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.6.12.tgz#78b75933505d376da6e5a28e9b7887e0094db8b5"
+ dependencies:
+ config-chain "~1.1.5"
+ editorconfig "^0.13.2"
+ mkdirp "~0.5.0"
+ nopt "~3.0.1"
+
js-cookie@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.1.3.tgz#48071625217ac9ecfab8c343a13d42ec09ff0526"
@@ -2715,13 +3117,20 @@ 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:
+js-yaml@3.x, js-yaml@^3.4.3, 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"
+js-yaml@~3.7.0:
+ version "3.7.0"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80"
+ dependencies:
+ argparse "^1.0.7"
+ esprima "^2.6.0"
+
jsbn@~0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.0.tgz#650987da0dd74f4ebf5a11377a2aa2d273e97dfd"
@@ -2883,6 +3292,12 @@ klaw@^1.0.0:
optionalDependencies:
graceful-fs "^4.1.9"
+latest-version@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-1.0.1.tgz#72cfc46e3e8d1be651e1ebb54ea9f6ea96f374bb"
+ dependencies:
+ package-json "^1.0.0"
+
lazy-cache@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e"
@@ -2929,7 +3344,7 @@ loader-utils@^0.2.11, loader-utils@^0.2.16, loader-utils@^0.2.5:
json5 "^0.5.0"
object-assign "^4.0.1"
-loader-utils@^1.0.2:
+loader-utils@^1.0.2, loader-utils@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd"
dependencies:
@@ -2944,16 +3359,55 @@ locate-path@^2.0.0:
p-locate "^2.0.0"
path-exists "^3.0.0"
+lodash._baseassign@^3.0.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz#8c38a099500f215ad09e59f1722fd0c52bfe0a4e"
+ dependencies:
+ lodash._basecopy "^3.0.0"
+ lodash.keys "^3.0.0"
+
+lodash._basecopy@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36"
+
lodash._baseget@^3.0.0:
version "3.7.2"
resolved "https://registry.yarnpkg.com/lodash._baseget/-/lodash._baseget-3.7.2.tgz#1b6ae1d5facf3c25532350a13c1197cb8bb674f4"
+lodash._bindcallback@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e"
+
+lodash._createassigner@^3.0.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz#838a5bae2fdaca63ac22dee8e19fa4e6d6970b11"
+ dependencies:
+ lodash._bindcallback "^3.0.0"
+ lodash._isiterateecall "^3.0.0"
+ lodash.restparam "^3.0.0"
+
+lodash._getnative@^3.0.0:
+ version "3.9.1"
+ resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5"
+
+lodash._isiterateecall@^3.0.0:
+ version "3.0.9"
+ resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c"
+
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.assign@^3.0.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-3.2.0.tgz#3ce9f0234b4b2223e296b8fa0ac1fee8ebca64fa"
+ dependencies:
+ lodash._baseassign "^3.0.0"
+ lodash._createassigner "^3.0.0"
+ lodash.keys "^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"
@@ -2962,6 +3416,10 @@ lodash.camelcase@4.1.1:
lodash.deburr "^4.0.0"
lodash.words "^4.0.0"
+lodash.camelcase@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
+
lodash.capitalize@^4.0.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz#f826c9b4e2a8511d84e3aca29db05e1a4f3b72a9"
@@ -2974,6 +3432,13 @@ lodash.deburr@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/lodash.deburr/-/lodash.deburr-4.1.0.tgz#ddb1bbb3ef07458c0177ba07de14422cb033ff9b"
+lodash.defaults@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-3.1.2.tgz#c7308b18dbf8bc9372d701a73493c61192bd2e2c"
+ dependencies:
+ lodash.assign "^3.0.0"
+ lodash.restparam "^3.0.0"
+
lodash.get@4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
@@ -2985,6 +3450,10 @@ lodash.get@^3.7.0:
lodash._baseget "^3.0.0"
lodash._topath "^3.0.0"
+lodash.isarguments@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
+
lodash.isarray@^3.0.0:
version "3.0.4"
resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55"
@@ -2996,6 +3465,22 @@ lodash.kebabcase@4.0.1:
lodash.deburr "^4.0.0"
lodash.words "^4.0.0"
+lodash.keys@^3.0.0:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a"
+ dependencies:
+ lodash._getnative "^3.0.0"
+ lodash.isarguments "^3.0.0"
+ lodash.isarray "^3.0.0"
+
+lodash.memoize@^4.1.2:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
+
+lodash.restparam@^3.0.0:
+ version "3.6.1"
+ resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805"
+
lodash.snakecase@4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.0.1.tgz#bd012e5d2f93f7b58b9303e9a7fbfd5db13d6281"
@@ -3003,6 +3488,10 @@ lodash.snakecase@4.0.1:
lodash.deburr "^4.0.0"
lodash.words "^4.0.0"
+lodash.uniq@^4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
+
lodash.words@^4.0.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/lodash.words/-/lodash.words-4.2.0.tgz#5ecfeaf8ecf8acaa8e0c8386295f1993c9cf4036"
@@ -3032,10 +3521,39 @@ loose-envify@^1.0.0:
dependencies:
js-tokens "^3.0.0"
+lowercase-keys@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306"
+
lru-cache@2.2.x:
version "2.2.4"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.2.4.tgz#6c658619becf14031d0d0b594b16042ce4dc063d"
+lru-cache@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-3.2.0.tgz#71789b3b7f5399bec8565dda38aa30d2a097efee"
+ dependencies:
+ pseudomap "^1.0.1"
+
+lru-cache@^4.0.1:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.0.2.tgz#1d17679c069cda5d040991a09dbc2c0db377e55e"
+ dependencies:
+ pseudomap "^1.0.1"
+ yallist "^2.0.0"
+
+macaddress@^0.2.8:
+ version "0.2.8"
+ resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12"
+
+map-stream@~0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194"
+
+math-expression-evaluator@^1.2.14:
+ version "1.2.16"
+ resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.16.tgz#b357fa1ca9faefb8e48d10c14ef2bcb2d9f0a7c9"
+
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
@@ -3102,7 +3620,7 @@ 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:
+"minimatch@2 || 3", minimatch@3.0.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:
@@ -3160,6 +3678,12 @@ negotiator@0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
+nested-error-stacks@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-1.0.2.tgz#19f619591519f096769a5ba9a86e6eeec823c3cf"
+ dependencies:
+ inherits "~2.0.1"
+
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"
@@ -3239,12 +3763,33 @@ node-zopfli@^2.0.0:
nan "^2.0.0"
node-pre-gyp "^0.6.4"
-nopt@3.x, nopt@~3.0.6:
+nodemon@^1.11.0:
+ version "1.11.0"
+ resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.11.0.tgz#226c562bd2a7b13d3d7518b49ad4828a3623d06c"
+ dependencies:
+ chokidar "^1.4.3"
+ debug "^2.2.0"
+ es6-promise "^3.0.2"
+ ignore-by-default "^1.0.0"
+ lodash.defaults "^3.1.2"
+ minimatch "^3.0.0"
+ ps-tree "^1.0.1"
+ touch "1.0.0"
+ undefsafe "0.0.3"
+ update-notifier "0.5.0"
+
+nopt@3.x, nopt@~3.0.1, nopt@~3.0.6:
version "3.0.6"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
dependencies:
abbrev "1"
+nopt@~1.0.10:
+ version "1.0.10"
+ resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee"
+ 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"
@@ -3258,6 +3803,19 @@ normalize-path@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.0.1.tgz#47886ac1662760d4261b7d979d241709d3ce3f7a"
+normalize-range@^0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
+
+normalize-url@^1.4.0:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c"
+ dependencies:
+ object-assign "^4.0.1"
+ prepend-http "^1.0.0"
+ query-string "^4.1.0"
+ sort-keys "^1.0.0"
+
npmlog@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.0.2.tgz#d03950e0e78ce1527ba26d2a7592e9348ac3e75f"
@@ -3267,6 +3825,10 @@ npmlog@^4.0.1:
gauge "~2.7.1"
set-blocking "~2.0.0"
+num2fraction@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede"
+
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"
@@ -3279,6 +3841,10 @@ 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@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2"
+
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"
@@ -3314,7 +3880,7 @@ once@1.x, once@^1.3.0, once@^1.4.0:
dependencies:
wrappy "1"
-once@~1.3.3:
+once@~1.3.0, once@~1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/once/-/once-1.3.3.tgz#b2e261557ce4c314ec8304f3fa82663e4297ca20"
dependencies:
@@ -3367,7 +3933,7 @@ 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:
+os-homedir@^1.0.0, os-homedir@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
@@ -3377,10 +3943,17 @@ os-locale@^1.4.0:
dependencies:
lcid "^1.0.0"
-os-tmpdir@^1.0.1, os-tmpdir@~1.0.1:
+os-tmpdir@^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"
+osenv@^0.1.0:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644"
+ dependencies:
+ os-homedir "^1.0.0"
+ os-tmpdir "^1.0.0"
+
p-limit@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc"
@@ -3391,6 +3964,13 @@ p-locate@^2.0.0:
dependencies:
p-limit "^1.1.0"
+package-json@^1.0.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/package-json/-/package-json-1.2.0.tgz#c8ecac094227cdf76a316874ed05e27cc939a0e0"
+ dependencies:
+ got "^3.2.0"
+ registry-url "^3.0.0"
+
pako@~0.2.0:
version "0.2.9"
resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75"
@@ -3484,6 +4064,12 @@ path-type@^1.0.0:
pify "^2.0.0"
pinkie-promise "^2.0.0"
+pause-stream@0.0.11:
+ version "0.0.11"
+ resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445"
+ dependencies:
+ through "~2.3"
+
pbkdf2@^3.0.3:
version "3.0.9"
resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.9.tgz#f2c4b25a600058b3c3773c086c37dbbee1ffe693"
@@ -3552,10 +4138,275 @@ portfinder@^1.0.9:
debug "^2.2.0"
mkdirp "0.5.x"
+postcss-calc@^5.2.0:
+ version "5.3.1"
+ resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-5.3.1.tgz#77bae7ca928ad85716e2fda42f261bf7c1d65b5e"
+ dependencies:
+ postcss "^5.0.2"
+ postcss-message-helpers "^2.0.0"
+ reduce-css-calc "^1.2.6"
+
+postcss-colormin@^2.1.8:
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-2.2.2.tgz#6631417d5f0e909a3d7ec26b24c8a8d1e4f96e4b"
+ dependencies:
+ colormin "^1.0.5"
+ postcss "^5.0.13"
+ postcss-value-parser "^3.2.3"
+
+postcss-convert-values@^2.3.4:
+ version "2.6.1"
+ resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-2.6.1.tgz#bbd8593c5c1fd2e3d1c322bb925dcae8dae4d62d"
+ dependencies:
+ postcss "^5.0.11"
+ postcss-value-parser "^3.1.2"
+
+postcss-discard-comments@^2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz#befe89fafd5b3dace5ccce51b76b81514be00e3d"
+ dependencies:
+ postcss "^5.0.14"
+
+postcss-discard-duplicates@^2.0.1:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-2.1.0.tgz#b9abf27b88ac188158a5eb12abcae20263b91932"
+ dependencies:
+ postcss "^5.0.4"
+
+postcss-discard-empty@^2.0.1:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz#d2b4bd9d5ced5ebd8dcade7640c7d7cd7f4f92b5"
+ dependencies:
+ postcss "^5.0.14"
+
+postcss-discard-overridden@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz#8b1eaf554f686fb288cd874c55667b0aa3668d58"
+ dependencies:
+ postcss "^5.0.16"
+
+postcss-discard-unused@^2.2.1:
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz#bce30b2cc591ffc634322b5fb3464b6d934f4433"
+ dependencies:
+ postcss "^5.0.14"
+ uniqs "^2.0.0"
+
+postcss-filter-plugins@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/postcss-filter-plugins/-/postcss-filter-plugins-2.0.2.tgz#6d85862534d735ac420e4a85806e1f5d4286d84c"
+ dependencies:
+ postcss "^5.0.4"
+ uniqid "^4.0.0"
+
+postcss-load-config@^1.1.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-1.2.0.tgz#539e9afc9ddc8620121ebf9d8c3673e0ce50d28a"
+ dependencies:
+ cosmiconfig "^2.1.0"
+ object-assign "^4.1.0"
+ postcss-load-options "^1.2.0"
+ postcss-load-plugins "^2.3.0"
+
+postcss-load-options@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/postcss-load-options/-/postcss-load-options-1.2.0.tgz#b098b1559ddac2df04bc0bb375f99a5cfe2b6d8c"
+ dependencies:
+ cosmiconfig "^2.1.0"
+ object-assign "^4.1.0"
+
+postcss-load-plugins@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/postcss-load-plugins/-/postcss-load-plugins-2.3.0.tgz#745768116599aca2f009fad426b00175049d8d92"
+ dependencies:
+ cosmiconfig "^2.1.1"
+ object-assign "^4.1.0"
+
+postcss-merge-idents@^2.1.5:
+ version "2.1.7"
+ resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz#4c5530313c08e1d5b3bbf3d2bbc747e278eea270"
+ dependencies:
+ has "^1.0.1"
+ postcss "^5.0.10"
+ postcss-value-parser "^3.1.1"
+
+postcss-merge-longhand@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-2.0.2.tgz#23d90cd127b0a77994915332739034a1a4f3d658"
+ dependencies:
+ postcss "^5.0.4"
+
+postcss-merge-rules@^2.0.3:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-2.1.2.tgz#d1df5dfaa7b1acc3be553f0e9e10e87c61b5f721"
+ dependencies:
+ browserslist "^1.5.2"
+ caniuse-api "^1.5.2"
+ postcss "^5.0.4"
+ postcss-selector-parser "^2.2.2"
+ vendors "^1.0.0"
+
+postcss-message-helpers@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz#a4f2f4fab6e4fe002f0aed000478cdf52f9ba60e"
+
+postcss-minify-font-values@^1.0.2:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz#4b58edb56641eba7c8474ab3526cafd7bbdecb69"
+ dependencies:
+ object-assign "^4.0.1"
+ postcss "^5.0.4"
+ postcss-value-parser "^3.0.2"
+
+postcss-minify-gradients@^1.0.1:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz#5dbda11373703f83cfb4a3ea3881d8d75ff5e6e1"
+ dependencies:
+ postcss "^5.0.12"
+ postcss-value-parser "^3.3.0"
+
+postcss-minify-params@^1.0.4:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz#ad2ce071373b943b3d930a3fa59a358c28d6f1f3"
+ dependencies:
+ alphanum-sort "^1.0.1"
+ postcss "^5.0.2"
+ postcss-value-parser "^3.0.2"
+ uniqs "^2.0.0"
+
+postcss-minify-selectors@^2.0.4:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz#b2c6a98c0072cf91b932d1a496508114311735bf"
+ dependencies:
+ alphanum-sort "^1.0.2"
+ has "^1.0.1"
+ postcss "^5.0.14"
+ postcss-selector-parser "^2.0.0"
+
+postcss-modules-extract-imports@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.0.1.tgz#8fb3fef9a6dd0420d3f6d4353cf1ff73f2b2a341"
+ dependencies:
+ postcss "^5.0.4"
+
+postcss-modules-local-by-default@^1.0.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.1.1.tgz#29a10673fa37d19251265ca2ba3150d9040eb4ce"
+ dependencies:
+ css-selector-tokenizer "^0.6.0"
+ postcss "^5.0.4"
+
+postcss-modules-scope@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-1.0.2.tgz#ff977395e5e06202d7362290b88b1e8cd049de29"
+ dependencies:
+ css-selector-tokenizer "^0.6.0"
+ postcss "^5.0.4"
+
+postcss-modules-values@^1.1.0:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-1.2.2.tgz#f0e7d476fe1ed88c5e4c7f97533a3e772ad94ca1"
+ dependencies:
+ icss-replace-symbols "^1.0.2"
+ postcss "^5.0.14"
+
+postcss-normalize-charset@^1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz#ef9ee71212d7fe759c78ed162f61ed62b5cb93f1"
+ dependencies:
+ postcss "^5.0.5"
+
+postcss-normalize-url@^3.0.7:
+ version "3.0.8"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz#108f74b3f2fcdaf891a2ffa3ea4592279fc78222"
+ dependencies:
+ is-absolute-url "^2.0.0"
+ normalize-url "^1.4.0"
+ postcss "^5.0.14"
+ postcss-value-parser "^3.2.3"
+
+postcss-ordered-values@^2.1.0:
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-2.2.3.tgz#eec6c2a67b6c412a8db2042e77fe8da43f95c11d"
+ dependencies:
+ postcss "^5.0.4"
+ postcss-value-parser "^3.0.1"
+
+postcss-reduce-idents@^2.2.2:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz#c2c6d20cc958284f6abfbe63f7609bf409059ad3"
+ dependencies:
+ postcss "^5.0.4"
+ postcss-value-parser "^3.0.2"
+
+postcss-reduce-initial@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz#68f80695f045d08263a879ad240df8dd64f644ea"
+ dependencies:
+ postcss "^5.0.4"
+
+postcss-reduce-transforms@^1.0.3:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz#ff76f4d8212437b31c298a42d2e1444025771ae1"
+ dependencies:
+ has "^1.0.1"
+ postcss "^5.0.8"
+ postcss-value-parser "^3.0.1"
+
+postcss-selector-parser@^2.0.0, postcss-selector-parser@^2.2.2:
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz#f9437788606c3c9acee16ffe8d8b16297f27bb90"
+ dependencies:
+ flatten "^1.0.2"
+ indexes-of "^1.0.1"
+ uniq "^1.0.1"
+
+postcss-svgo@^2.1.1:
+ version "2.1.6"
+ resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-2.1.6.tgz#b6df18aa613b666e133f08adb5219c2684ac108d"
+ dependencies:
+ is-svg "^2.0.0"
+ postcss "^5.0.14"
+ postcss-value-parser "^3.2.3"
+ svgo "^0.7.0"
+
+postcss-unique-selectors@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz#981d57d29ddcb33e7b1dfe1fd43b8649f933ca1d"
+ dependencies:
+ alphanum-sort "^1.0.1"
+ postcss "^5.0.4"
+ uniqs "^2.0.0"
+
+postcss-value-parser@^3.0.1, postcss-value-parser@^3.0.2, postcss-value-parser@^3.1.1, postcss-value-parser@^3.1.2, postcss-value-parser@^3.2.3, postcss-value-parser@^3.3.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz#87f38f9f18f774a4ab4c8a232f5c5ce8872a9d15"
+
+postcss-zindex@^2.0.1:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-2.2.0.tgz#d2109ddc055b91af67fc4cb3b025946639d2af22"
+ dependencies:
+ has "^1.0.1"
+ postcss "^5.0.4"
+ uniqs "^2.0.0"
+
+postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0.14, postcss@^5.0.16, postcss@^5.0.2, postcss@^5.0.21, postcss@^5.0.4, postcss@^5.0.5, postcss@^5.0.6, postcss@^5.0.8, postcss@^5.2.16:
+ version "5.2.16"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.16.tgz#732b3100000f9ff8379a48a53839ed097376ad57"
+ dependencies:
+ chalk "^1.1.3"
+ js-base64 "^2.1.9"
+ source-map "^0.5.6"
+ supports-color "^3.2.3"
+
prelude-ls@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
+prepend-http@^1.0.0:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
+
preserve@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
@@ -3576,6 +4427,10 @@ progress@^1.1.8, progress@~1.1.8:
version "1.1.8"
resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be"
+proto-list@~1.2.1:
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
+
proxy-addr@~1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.3.tgz#dc97502f5722e888467b3fa2297a7b1ff47df074"
@@ -3587,6 +4442,16 @@ prr@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a"
+ps-tree@^1.0.1:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/ps-tree/-/ps-tree-1.1.0.tgz#b421b24140d6203f1ed3c76996b4427b08e8c014"
+ dependencies:
+ event-stream "~3.3.0"
+
+pseudomap@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
+
public-encrypt@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.0.tgz#39f699f3a46560dd5ebacbca693caf7c65c18cc6"
@@ -3605,6 +4470,10 @@ punycode@^1.2.4, punycode@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
+q@^1.1.2:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/q/-/q-1.5.0.tgz#dd01bac9d06d30e6f219aecb8253ee9ebdc308f1"
+
qjobs@^1.1.4:
version "1.1.5"
resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.1.5.tgz#659de9f2cf8dcc27a1481276f205377272382e73"
@@ -3621,6 +4490,13 @@ qs@~6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.0.tgz#f403b264f23bc01228c74131b407f18d5ea5d442"
+query-string@^4.1.0:
+ version "4.3.2"
+ resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.2.tgz#ec0fd765f58a50031a3968c2431386f8947a5cdd"
+ dependencies:
+ object-assign "^4.1.0"
+ strict-uri-encode "^1.0.0"
+
querystring-es3@^0.2.0:
version "0.2.1"
resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
@@ -3666,7 +4542,7 @@ raw-loader@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-0.5.1.tgz#0c3d0beaed8a01c966d9787bf778281252a979aa"
-rc@~1.1.6:
+rc@^1.0.1, rc@~1.1.6:
version "1.1.6"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.1.6.tgz#43651b76b6ae53b5c802f1151fa3fc3b059969c9"
dependencies:
@@ -3675,6 +4551,28 @@ rc@~1.1.6:
minimist "^1.2.0"
strip-json-comments "~1.0.4"
+react-dev-utils@^0.5.2:
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-0.5.2.tgz#50d0b962d3a94b6c2e8f2011ed6468e4124bc410"
+ dependencies:
+ ansi-html "0.0.5"
+ chalk "1.1.3"
+ escape-string-regexp "1.0.5"
+ filesize "3.3.0"
+ gzip-size "3.0.0"
+ html-entities "1.2.0"
+ opn "4.0.2"
+ recursive-readdir "2.1.1"
+ sockjs-client "1.0.1"
+ strip-ansi "3.0.1"
+
+read-all-stream@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/read-all-stream/-/read-all-stream-3.1.0.tgz#35c3e177f2078ef789ee4bfafa4373074eaef4fa"
+ dependencies:
+ pinkie-promise "^2.0.0"
+ readable-stream "^2.0.0"
+
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"
@@ -3690,7 +4588,7 @@ read-pkg@^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.2, readable-stream@^2.1.0, readable-stream@^2.2.2:
+readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", 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:
@@ -3702,7 +4600,7 @@ read-pkg@^1.0.0:
string_decoder "~0.10.x"
util-deprecate "~1.0.1"
-readable-stream@^2.0.1, readable-stream@^2.0.5, readable-stream@~2.0.0, readable-stream@~2.0.6:
+readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@~2.0.0, readable-stream@~2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e"
dependencies:
@@ -3757,6 +4655,26 @@ rechoir@^0.6.2:
dependencies:
resolve "^1.1.6"
+recursive-readdir@2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.1.1.tgz#a01cfc7f7f38a53ec096a096f63a50489c3e297c"
+ dependencies:
+ minimatch "3.0.3"
+
+reduce-css-calc@^1.2.6:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716"
+ dependencies:
+ balanced-match "^0.4.2"
+ math-expression-evaluator "^1.2.14"
+ reduce-function-call "^1.0.1"
+
+reduce-function-call@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/reduce-function-call/-/reduce-function-call-1.0.2.tgz#5a200bf92e0e37751752fe45b0ab330fd4b6be99"
+ dependencies:
+ balanced-match "^0.4.2"
+
regenerate@^1.2.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.2.tgz#d1941c67bad437e1be76433add5b385f95b19260"
@@ -3780,6 +4698,14 @@ regex-cache@^0.4.2:
is-equal-shallow "^0.1.3"
is-primitive "^2.0.0"
+regexpu-core@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b"
+ dependencies:
+ regenerate "^1.2.1"
+ regjsgen "^0.2.0"
+ regjsparser "^0.1.4"
+
regexpu-core@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240"
@@ -3788,6 +4714,12 @@ regexpu-core@^2.0.0:
regjsgen "^0.2.0"
regjsparser "^0.1.4"
+registry-url@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942"
+ dependencies:
+ rc "^1.0.1"
+
regjsgen@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7"
@@ -3810,6 +4742,12 @@ repeat-string@^1.5.2:
version "1.6.1"
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
+repeating@^1.1.2:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/repeating/-/repeating-1.1.3.tgz#3d4114218877537494f97f77f9785fab810fa4ac"
+ dependencies:
+ is-finite "^1.0.0"
+
repeating@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda"
@@ -3851,6 +4789,10 @@ require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
+require-from-string@^1.1.0:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-1.2.1.tgz#529c9ccef27380adfec9a2f965b649bbee636418"
+
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"
@@ -3915,6 +4857,10 @@ safe-buffer@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7"
+sax@~1.2.1:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.2.tgz#fd8631a23bc7826bef5d871bdb87378c95647828"
+
select-hose@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
@@ -3923,7 +4869,13 @@ 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:
+semver-diff@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36"
+ dependencies:
+ semver "^5.0.3"
+
+"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.3.0, semver@~5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
@@ -4000,6 +4952,10 @@ shelljs@^0.7.5:
interpret "^1.0.0"
rechoir "^0.6.2"
+sigmund@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590"
+
signal-exit@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
@@ -4012,6 +4968,10 @@ slice-ansi@0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"
+slide@^1.1.5:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707"
+
sntp@1.x.x:
version "1.0.9"
resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198"
@@ -4062,6 +5022,17 @@ socket.io@1.7.2:
socket.io-client "1.7.2"
socket.io-parser "2.3.1"
+sockjs-client@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.0.1.tgz#8943ae05b46547bc2054816c409002cf5e2fe026"
+ dependencies:
+ debug "^2.1.0"
+ eventsource "^0.1.3"
+ faye-websocket "~0.7.3"
+ inherits "^2.0.1"
+ json3 "^3.3.2"
+ url-parse "^1.0.1"
+
sockjs-client@1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.2.tgz#f0212a8550e4c9468c8cceaeefd2e3493c033ad5"
@@ -4080,14 +5051,20 @@ sockjs@0.3.18:
faye-websocket "^0.10.0"
uuid "^2.0.2"
-source-list-map@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-1.1.1.tgz#1a33ac210ca144d1e561f906ebccab5669ff4cb4"
+sort-keys@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad"
+ dependencies:
+ is-plain-obj "^1.0.0"
-source-list-map@~0.1.7:
+source-list-map@^0.1.7, 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-list-map@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-1.1.1.tgz#1a33ac210ca144d1e561f906ebccab5669ff4cb4"
+
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"
@@ -4106,7 +5083,7 @@ source-map@^0.4.4:
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:
+source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, 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"
@@ -4150,6 +5127,12 @@ spdy@^3.4.1:
select-hose "^2.0.0"
spdy-transport "^2.0.15"
+split@0.3:
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/split/-/split-0.3.3.tgz#cd0eea5e63a211dfff7eb0f091c4133e2d0dd28f"
+ dependencies:
+ through "2"
+
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
@@ -4184,6 +5167,12 @@ stream-browserify@^2.0.1:
inherits "~2.0.1"
readable-stream "^2.0.2"
+stream-combiner@~0.0.4:
+ version "0.0.4"
+ resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.0.4.tgz#4d5e433c185261dde623ca3f44c586bcf5c4ad14"
+ dependencies:
+ duplexer "~0.1.1"
+
stream-http@^2.3.1:
version "2.6.3"
resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.6.3.tgz#4c3ddbf9635968ea2cfd4e48d43de5def2625ac3"
@@ -4194,6 +5183,20 @@ stream-http@^2.3.1:
to-arraybuffer "^1.0.0"
xtend "^4.0.0"
+stream-shift@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
+
+strict-uri-encode@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
+
+string-length@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/string-length/-/string-length-1.0.1.tgz#56970fb1c38558e9e70b728bf3de269ac45adfac"
+ dependencies:
+ strip-ansi "^3.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"
@@ -4217,7 +5220,7 @@ 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:
+strip-ansi@3.0.1, 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:
@@ -4249,12 +5252,24 @@ 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:
+supports-color@^3.1.0, supports-color@^3.1.1, supports-color@^3.1.2, supports-color@^3.2.3:
version "3.2.3"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6"
dependencies:
has-flag "^1.0.0"
+svgo@^0.7.0:
+ version "0.7.2"
+ resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5"
+ dependencies:
+ coa "~1.0.1"
+ colors "~1.1.2"
+ csso "~2.3.1"
+ js-yaml "~3.7.0"
+ mkdirp "~0.5.1"
+ sax "~1.2.1"
+ whet.extend "~0.9.9"
+
table@^3.7.8:
version "3.8.3"
resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f"
@@ -4325,7 +5340,7 @@ throttleit@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c"
-through@^2.3.6:
+through@2, through@^2.3.6, through@~2.3, through@~2.3.1:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
@@ -4333,6 +5348,10 @@ timeago.js@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/timeago.js/-/timeago.js-2.0.5.tgz#730c74fbdb0b0917a553675a4460e3a7f80db86c"
+timed-out@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-2.0.0.tgz#f38b0ae81d3747d628001f41dafc652ace671c0a"
+
timers-browserify@^1.4.2:
version "1.4.2"
resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-1.4.2.tgz#c9c58b575be8407375cb5e2462dacee74359f41d"
@@ -4363,6 +5382,12 @@ 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"
+touch@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/touch/-/touch-1.0.0.tgz#449cbe2dbae5a8c8038e30d71fa0ff464947c4de"
+ dependencies:
+ nopt "~1.0.10"
+
tough-cookie@~2.3.0:
version "2.3.2"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a"
@@ -4435,14 +5460,44 @@ 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"
+undefsafe@0.0.3:
+ version "0.0.3"
+ resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-0.0.3.tgz#ecca3a03e56b9af17385baac812ac83b994a962f"
+
underscore@^1.8.3:
version "1.8.3"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022"
+uniq@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
+
+uniqid@^4.0.0:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/uniqid/-/uniqid-4.1.1.tgz#89220ddf6b751ae52b5f72484863528596bb84c1"
+ dependencies:
+ macaddress "^0.2.8"
+
+uniqs@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02"
+
unpipe@1.0.0, unpipe@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
+update-notifier@0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-0.5.0.tgz#07b5dc2066b3627ab3b4f530130f7eddda07a4cc"
+ dependencies:
+ chalk "^1.0.0"
+ configstore "^1.0.0"
+ is-npm "^1.0.0"
+ latest-version "^1.0.0"
+ repeating "^1.1.2"
+ semver-diff "^2.0.0"
+ string-length "^1.0.0"
+
url-parse@1.0.x:
version "1.0.5"
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.0.5.tgz#0854860422afdcfefeb6c965c662d4800169927b"
@@ -4450,7 +5505,7 @@ url-parse@1.0.x:
querystringify "0.0.x"
requires-port "1.0.x"
-url-parse@^1.1.1:
+url-parse@^1.0.1, url-parse@^1.1.1:
version "1.1.7"
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.1.7.tgz#025cff999653a459ab34232147d89514cc87d74a"
dependencies:
@@ -4491,7 +5546,7 @@ 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:
+uuid@^2.0.1, uuid@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a"
@@ -4510,6 +5565,10 @@ vary@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.0.tgz#e1e5affbbd16ae768dd2674394b9ad3022653140"
+vendors@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.1.tgz#37ad73c8ee417fb3d580e785312307d274847f22"
+
verror@1.3.6:
version "1.3.6"
resolved "https://registry.yarnpkg.com/verror/-/verror-1.3.6.tgz#cff5df12946d297d2baaefaa2689e25be01c005c"
@@ -4530,13 +5589,52 @@ void-elements@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
+vue-hot-reload-api@^2.0.11:
+ version "2.0.11"
+ resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.0.11.tgz#bf26374fb73366ce03f799e65ef5dfd0e28a1568"
+
+vue-loader@^11.3.4:
+ version "11.3.4"
+ resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-11.3.4.tgz#65e10a44ce092d906e14bbc72981dec99eb090d2"
+ dependencies:
+ consolidate "^0.14.0"
+ hash-sum "^1.0.2"
+ js-beautify "^1.6.3"
+ loader-utils "^1.1.0"
+ lru-cache "^4.0.1"
+ postcss "^5.0.21"
+ postcss-load-config "^1.1.0"
+ postcss-selector-parser "^2.0.0"
+ source-map "^0.5.6"
+ vue-hot-reload-api "^2.0.11"
+ vue-style-loader "^2.0.0"
+ vue-template-es2015-compiler "^1.2.2"
+
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.2.4:
- version "2.2.4"
- resolved "https://registry.yarnpkg.com/vue/-/vue-2.2.4.tgz#d0a3a050a80a12356d7950ae5a7b3131048209cc"
+vue-style-loader@^2.0.0:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-2.0.5.tgz#f0efac992febe3f12e493e334edb13cd235a3d22"
+ dependencies:
+ hash-sum "^1.0.2"
+ loader-utils "^1.0.2"
+
+vue-template-compiler@^2.2.6:
+ version "2.2.6"
+ resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.2.6.tgz#2e2928daf0cd0feca9dfc35a9729adeae173ec68"
+ dependencies:
+ de-indent "^1.0.2"
+ he "^1.1.0"
+
+vue-template-es2015-compiler@^1.2.2:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.5.1.tgz#0c36cc57aa3a9ec13e846342cb14a72fcac8bd93"
+
+vue@^2.2.6:
+ version "2.2.6"
+ resolved "https://registry.yarnpkg.com/vue/-/vue-2.2.6.tgz#451714b394dd6d4eae7b773c40c2034a59621aed"
watchpack@^1.3.1:
version "1.3.1"
@@ -4637,7 +5735,7 @@ webpack@^2.3.3:
webpack-sources "^0.2.3"
yargs "^6.0.0"
-websocket-driver@>=0.5.1:
+websocket-driver@>=0.3.6, websocket-driver@>=0.5.1:
version "0.6.5"
resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.6.5.tgz#5cb2556ceb85f4373c6d8238aa691c8454e13a36"
dependencies:
@@ -4647,6 +5745,10 @@ websocket-extensions@>=0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.1.tgz#76899499c184b6ef754377c2dbb0cd6cb55d29e7"
+whet.extend@~0.9.9:
+ version "0.9.9"
+ resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1"
+
which-module@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"
@@ -4690,6 +5792,14 @@ wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+write-file-atomic@^1.1.2:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-1.3.1.tgz#7d45ba32316328dd1ec7d90f60ebc0d845bb759a"
+ dependencies:
+ graceful-fs "^4.1.11"
+ imurmurhash "^0.1.4"
+ slide "^1.1.5"
+
write@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757"
@@ -4707,6 +5817,12 @@ wtf-8@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/wtf-8/-/wtf-8-1.0.0.tgz#392d8ba2d0f1c34d1ee2d630f15d0efb68e1048a"
+xdg-basedir@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-2.0.0.tgz#edbc903cc385fc04523d966a335504b5504d1bd2"
+ dependencies:
+ os-homedir "^1.0.0"
+
xmlhttprequest-ssl@1.5.3:
version "1.5.3"
resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz#185a888c04eca46c3e4070d99f7b49de3528992d"
@@ -4719,6 +5835,10 @@ y18n@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
+yallist@^2.0.0:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
+
yargs-parser@^4.2.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-4.2.1.tgz#29cceac0dc4f03c6c87b4a9f217dd18c9f74871c"