summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorConstance Okoghenun <cokoghenun@gitlab.com>2018-01-11 16:27:30 +0100
committerConstance Okoghenun <cokoghenun@gitlab.com>2018-01-11 16:27:30 +0100
commit0fe784cbfb56a701259d83c989341afb1b7bc124 (patch)
tree099ec5f39984c031b2403f75b1958be93e46d882
parent7d0a1285a10fcb4735c20e21d83367159402a06d (diff)
parent27732df37178c3a07c99304406dfdb41fa8b3670 (diff)
downloadgitlab-ce-help-profiles-dispatcher-refactor.tar.gz
Resolved conflicts in dispatcher.jshelp-profiles-dispatcher-refactor
-rw-r--r--.babelrc3
-rw-r--r--.codeclimate.yml3
-rw-r--r--.eslintrc22
-rw-r--r--.gitlab-ci.yml13
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile6
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/images/multi-editor-on.pngbin5464 -> 3971 bytes
-rw-r--r--app/assets/javascripts/blob/notebook/index.js76
-rw-r--r--app/assets/javascripts/blob/pdf/index.js6
-rw-r--r--app/assets/javascripts/boards/boards_bundle.js18
-rw-r--r--app/assets/javascripts/boards/components/board_card.vue42
-rw-r--r--app/assets/javascripts/clusters/components/application_row.vue200
-rw-r--r--app/assets/javascripts/clusters/components/applications.vue200
-rw-r--r--app/assets/javascripts/commit/image_file.js2
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_table.vue18
-rw-r--r--app/assets/javascripts/cycle_analytics/components/banner.vue18
-rw-r--r--app/assets/javascripts/cycle_analytics/components/limit_warning_component.vue21
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_code_component.vue42
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_component.vue39
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_plan_component.vue35
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_review_component.vue53
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue62
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_test_component.vue66
-rw-r--r--app/assets/javascripts/cycle_analytics/components/total_time_component.vue30
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js20
-rw-r--r--app/assets/javascripts/deploy_keys/components/action_btn.vue24
-rw-r--r--app/assets/javascripts/deploy_keys/components/app.vue46
-rw-r--r--app/assets/javascripts/deploy_keys/components/key.vue9
-rw-r--r--app/assets/javascripts/deploy_keys/components/keys_panel.vue12
-rw-r--r--app/assets/javascripts/deploy_keys/index.js6
-rw-r--r--app/assets/javascripts/dispatcher.js155
-rw-r--r--app/assets/javascripts/environments/components/container.vue19
-rw-r--r--app/assets/javascripts/environments/components/empty_state.vue14
-rw-r--r--app/assets/javascripts/environments/components/environment_actions.vue95
-rw-r--r--app/assets/javascripts/environments/components/environment_external_url.vue46
-rw-r--r--app/assets/javascripts/environments/components/environment_item.vue900
-rw-r--r--app/assets/javascripts/environments/components/environment_monitoring.vue42
-rw-r--r--app/assets/javascripts/environments/components/environment_rollback.vue73
-rw-r--r--app/assets/javascripts/environments/components/environment_stop.vue83
-rw-r--r--app/assets/javascripts/environments/components/environment_terminal_button.vue55
-rw-r--r--app/assets/javascripts/environments/components/environments_app.vue31
-rw-r--r--app/assets/javascripts/environments/components/environments_table.vue79
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_view.vue15
-rw-r--r--app/assets/javascripts/filtered_search/recent_searches_root.js6
-rw-r--r--app/assets/javascripts/groups/components/app.vue84
-rw-r--r--app/assets/javascripts/groups/components/group_folder.vue11
-rw-r--r--app/assets/javascripts/groups/components/group_item.vue12
-rw-r--r--app/assets/javascripts/groups/components/groups.vue69
-rw-r--r--app/assets/javascripts/groups/components/item_actions.vue26
-rw-r--r--app/assets/javascripts/groups/components/item_caret.vue6
-rw-r--r--app/assets/javascripts/groups/components/item_stats.vue67
-rw-r--r--app/assets/javascripts/groups/components/item_stats_value.vue90
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/list.vue1
-rw-r--r--app/assets/javascripts/ide/components/ide.vue108
-rw-r--r--app/assets/javascripts/ide/components/ide_context_bar.vue118
-rw-r--r--app/assets/javascripts/ide/components/ide_project_branches_tree.vue10
-rw-r--r--app/assets/javascripts/ide/components/ide_project_tree.vue14
-rw-r--r--app/assets/javascripts/ide/components/ide_repo_tree.vue49
-rw-r--r--app/assets/javascripts/ide/components/ide_side_bar.vue148
-rw-r--r--app/assets/javascripts/ide/components/ide_status_bar.vue79
-rw-r--r--app/assets/javascripts/ide/components/new_branch_form.vue14
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/index.vue10
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/modal.vue44
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/upload.vue12
-rw-r--r--app/assets/javascripts/ide/components/repo_commit_section.vue131
-rw-r--r--app/assets/javascripts/ide/components/repo_edit_button.vue2
-rw-r--r--app/assets/javascripts/ide/components/repo_editor.vue64
-rw-r--r--app/assets/javascripts/ide/components/repo_file.vue22
-rw-r--r--app/assets/javascripts/ide/components/repo_file_buttons.vue12
-rw-r--r--app/assets/javascripts/ide/components/repo_loading_file.vue6
-rw-r--r--app/assets/javascripts/ide/components/repo_preview.vue118
-rw-r--r--app/assets/javascripts/ide/components/repo_tab.vue71
-rw-r--r--app/assets/javascripts/issue_show/components/app.vue564
-rw-r--r--app/assets/javascripts/issue_show/components/description.vue18
-rw-r--r--app/assets/javascripts/issue_show/components/edited.vue52
-rw-r--r--app/assets/javascripts/issue_show/components/fields/description.vue6
-rw-r--r--app/assets/javascripts/issue_show/components/form.vue26
-rw-r--r--app/assets/javascripts/issue_show/components/title.vue30
-rw-r--r--app/assets/javascripts/jobs/components/header.vue22
-rw-r--r--app/assets/javascripts/jobs/components/sidebar_detail_row.vue7
-rw-r--r--app/assets/javascripts/jobs/components/sidebar_details_block.vue60
-rw-r--r--app/assets/javascripts/jobs/job_details_bundle.js12
-rw-r--r--app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js2
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue62
-rw-r--r--app/assets/javascripts/monitoring/components/empty_state.vue24
-rw-r--r--app/assets/javascripts/monitoring/components/graph.vue126
-rw-r--r--app/assets/javascripts/monitoring/components/graph/deployment.vue22
-rw-r--r--app/assets/javascripts/monitoring/components/graph/flag.vue45
-rw-r--r--app/assets/javascripts/monitoring/components/graph/legend.vue77
-rw-r--r--app/assets/javascripts/monitoring/components/graph/path.vue9
-rw-r--r--app/assets/javascripts/monitoring/components/graph_group.vue18
-rw-r--r--app/assets/javascripts/notebook/cells/markdown.vue21
-rw-r--r--app/assets/javascripts/notebook/cells/output/html.vue22
-rw-r--r--app/assets/javascripts/notebook/cells/output/image.vue31
-rw-r--r--app/assets/javascripts/notebook/cells/output/index.vue134
-rw-r--r--app/assets/javascripts/notebook/cells/prompt.vue19
-rw-r--r--app/assets/javascripts/notebook/index.vue10
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue85
-rw-r--r--app/assets/javascripts/notes/components/discussion_locked_widget.vue16
-rw-r--r--app/assets/javascripts/notes/components/note_actions.vue75
-rw-r--r--app/assets/javascripts/notes/components/note_attachment.vue10
-rw-r--r--app/assets/javascripts/notes/components/note_awards_list.vue18
-rw-r--r--app/assets/javascripts/notes/components/note_body.vue66
-rw-r--r--app/assets/javascripts/notes/components/note_edited_text.vue15
-rw-r--r--app/assets/javascripts/notes/components/note_form.vue59
-rw-r--r--app/assets/javascripts/notes/components/note_header.vue29
-rw-r--r--app/assets/javascripts/notes/components/noteable_discussion.vue126
-rw-r--r--app/assets/javascripts/notes/components/noteable_note.vue38
-rw-r--r--app/assets/javascripts/notes/components/notes_app.vue60
-rw-r--r--app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js (renamed from app/assets/javascripts/abuse_reports.js)2
-rw-r--r--app/assets/javascripts/pages/admin/abuse_reports/index.js3
-rw-r--r--app/assets/javascripts/pages/admin/admin.js (renamed from app/assets/javascripts/admin.js)2
-rw-r--r--app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js (renamed from app/assets/javascripts/broadcast_message.js)0
-rw-r--r--app/assets/javascripts/pages/admin/broadcast_messages/index.js3
-rw-r--r--app/assets/javascripts/pages/admin/cohorts/index.js3
-rw-r--r--app/assets/javascripts/pages/admin/cohorts/usage_ping.js (renamed from app/assets/javascripts/usage_ping.js)0
-rw-r--r--app/assets/javascripts/pages/admin/conversational_development_index/show/index.js3
-rw-r--r--app/assets/javascripts/pages/admin/groups/edit/index.js3
-rw-r--r--app/assets/javascripts/pages/admin/groups/new/index.js9
-rw-r--r--app/assets/javascripts/pages/admin/groups/show/index.js3
-rw-r--r--app/assets/javascripts/pages/admin/impersonation_tokens/index.js3
-rw-r--r--app/assets/javascripts/pages/admin/index.js3
-rw-r--r--app/assets/javascripts/pages/admin/labels/edit/index.js3
-rw-r--r--app/assets/javascripts/pages/admin/labels/new/index.js3
-rw-r--r--app/assets/javascripts/pages/admin/projects/index.js9
-rw-r--r--app/assets/javascripts/pages/ci/lints/ci_lint_editor.js (renamed from app/assets/javascripts/ci_lint_editor.js)0
-rw-r--r--app/assets/javascripts/pages/ci/lints/index.js3
-rw-r--r--app/assets/javascripts/pages/dashboard/activity/index.js3
-rw-r--r--app/assets/javascripts/pages/dashboard/issues/index.js7
-rw-r--r--app/assets/javascripts/pages/dashboard/milestones/index/index.js3
-rw-r--r--app/assets/javascripts/pages/dashboard/milestones/show/index.js7
-rw-r--r--app/assets/javascripts/pages/dashboard/projects/index.js3
-rw-r--r--app/assets/javascripts/pages/explore/groups/index.js14
-rw-r--r--app/assets/javascripts/pages/explore/projects/index.js3
-rw-r--r--app/assets/javascripts/pages/import/fogbugz/new_user_map/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/activity/index.js7
-rw-r--r--app/assets/javascripts/pages/projects/artifacts/browse/index.js7
-rw-r--r--app/assets/javascripts/pages/projects/artifacts/file/index.js7
-rw-r--r--app/assets/javascripts/pages/search/show/index.js3
-rw-r--r--app/assets/javascripts/pages/search/show/search.js (renamed from app/assets/javascripts/search.js)4
-rw-r--r--app/assets/javascripts/pages/sessions/new/index.js11
-rw-r--r--app/assets/javascripts/pages/sessions/new/oauth_remember_me.js (renamed from app/assets/javascripts/oauth_remember_me.js)0
-rw-r--r--app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js (renamed from app/assets/javascripts/signin_tabs_memoizer.js)2
-rw-r--r--app/assets/javascripts/pages/sessions/new/username_validator.js (renamed from app/assets/javascripts/username_validator.js)0
-rw-r--r--app/assets/javascripts/pages/snippets/show/index.js12
-rw-r--r--app/assets/javascripts/pdf/index.vue22
-rw-r--r--app/assets/javascripts/pdf/page/index.vue32
-rw-r--r--app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue50
-rw-r--r--app/assets/javascripts/pipeline_schedules/components/pipeline_schedules_callout.vue20
-rw-r--r--app/assets/javascripts/pipelines/components/async_button.vue109
-rw-r--r--app/assets/javascripts/pipelines/components/empty_state.vue6
-rw-r--r--app/assets/javascripts/pipelines/components/graph/action_component.vue21
-rw-r--r--app/assets/javascripts/pipelines/components/graph/dropdown_action_component.vue20
-rw-r--r--app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue36
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component.vue16
-rw-r--r--app/assets/javascripts/pipelines/components/graph/job_component.vue31
-rw-r--r--app/assets/javascripts/pipelines/components/graph/job_name_component.vue12
-rw-r--r--app/assets/javascripts/pipelines/components/graph/stage_column_component.vue83
-rw-r--r--app/assets/javascripts/pipelines/components/header_component.vue132
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_url.vue28
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines.vue43
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_actions.vue25
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_artifacts.vue34
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_table.vue21
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_table_row.vue426
-rw-r--r--app/assets/javascripts/pipelines/components/stage.vue247
-rw-r--r--app/assets/javascripts/pipelines/components/time_ago.vue33
-rw-r--r--app/assets/javascripts/pipelines/pipeline_details_bundle.js12
-rw-r--r--app/assets/javascripts/pipelines/pipelines_bundle.js6
-rw-r--r--app/assets/javascripts/profile/account/components/delete_account_modal.vue28
-rw-r--r--app/assets/javascripts/projects/permissions/components/project_feature_setting.vue129
-rw-r--r--app/assets/javascripts/projects/permissions/components/project_setting_row.vue65
-rw-r--r--app/assets/javascripts/projects/permissions/components/settings_panel.vue328
-rw-r--r--app/assets/javascripts/projects_dropdown/components/app.vue32
-rw-r--r--app/assets/javascripts/projects_dropdown/components/projects_list_frequent.vue50
-rw-r--r--app/assets/javascripts/projects_dropdown/components/projects_list_item.vue131
-rw-r--r--app/assets/javascripts/projects_dropdown/components/search.vue81
-rw-r--r--app/assets/javascripts/registry/components/app.vue32
-rw-r--r--app/assets/javascripts/registry/components/collapsible_container.vue49
-rw-r--r--app/assets/javascripts/registry/components/table_registry.vue148
-rw-r--r--app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue99
-rw-r--r--app/assets/javascripts/sidebar/components/confidential/edit_form.vue37
-rw-r--r--app/assets/javascripts/sidebar/components/lock/edit_form.vue58
-rw-r--r--app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue106
-rw-r--r--app/assets/javascripts/sidebar/components/participants/participants.vue151
-rw-r--r--app/assets/javascripts/sidebar/components/participants/sidebar_participants.vue34
-rw-r--r--app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions.vue30
-rw-r--r--app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue74
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue38
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue24
-rw-r--r--app/assets/javascripts/vue_shared/components/ci_badge_link.vue17
-rw-r--r--app/assets/javascripts/vue_shared/components/ci_icon.vue13
-rw-r--r--app/assets/javascripts/vue_shared/components/clipboard_button.vue33
-rw-r--r--app/assets/javascripts/vue_shared/components/commit.vue47
-rw-r--r--app/assets/javascripts/vue_shared/components/expand_button.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/file_icon.vue14
-rw-r--r--app/assets/javascripts/vue_shared/components/header_ci_component.vue178
-rw-r--r--app/assets/javascripts/vue_shared/components/icon.vue21
-rw-r--r--app/assets/javascripts/vue_shared/components/identicon.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/issue/issue_warning.vue24
-rw-r--r--app/assets/javascripts/vue_shared/components/loading_button.vue121
-rw-r--r--app/assets/javascripts/vue_shared/components/loading_icon.vue5
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/field.vue90
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/header.vue66
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/toolbar.vue53
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue19
-rw-r--r--app/assets/javascripts/vue_shared/components/modal.vue250
-rw-r--r--app/assets/javascripts/vue_shared/components/navigation_tabs.vue11
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue16
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/placeholder_system_note.vue12
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/system_note.vue8
-rw-r--r--app/assets/javascripts/vue_shared/components/panel_resizer.vue148
-rw-r--r--app/assets/javascripts/vue_shared/components/pikaday.vue23
-rw-r--r--app/assets/javascripts/vue_shared/components/project_avatar/image.vue146
-rw-r--r--app/assets/javascripts/vue_shared/components/recaptcha_modal.vue133
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue12
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue17
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue10
-rw-r--r--app/assets/javascripts/vue_shared/components/table_pagination.vue249
-rw-r--r--app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue16
-rw-r--r--app/assets/javascripts/vue_shared/components/toggle_button.vue20
-rw-r--r--app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue8
-rw-r--r--app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue8
-rw-r--r--app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_svg.vue4
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss4
-rw-r--r--app/assets/stylesheets/framework/files.scss9
-rw-r--r--app/assets/stylesheets/framework/filters.scss2
-rw-r--r--app/assets/stylesheets/framework/variables.scss3
-rw-r--r--app/assets/stylesheets/pages/diff.scss8
-rw-r--r--app/assets/stylesheets/pages/issuable.scss1
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss2
-rw-r--r--app/assets/stylesheets/pages/projects.scss7
-rw-r--r--app/assets/stylesheets/pages/repo.scss6
-rw-r--r--app/controllers/projects/clusters/gcp_controller.rb31
-rw-r--r--app/helpers/blob_helper.rb2
-rw-r--r--app/models/merge_request_diff.rb11
-rw-r--r--app/models/project.rb2
-rw-r--r--app/models/repository.rb10
-rw-r--r--app/services/check_gcp_project_billing_service.rb5
-rw-r--r--app/services/issues/move_service.rb16
-rw-r--r--app/services/merge_requests/build_service.rb8
-rw-r--r--app/services/merge_requests/rebase_service.rb8
-rw-r--r--app/views/layouts/header/_default.html.haml6
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml6
-rw-r--r--app/views/layouts/nav/projects_dropdown/_show.html.haml4
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml2
-rw-r--r--app/views/profiles/preferences/show.html.haml4
-rw-r--r--app/views/projects/_home_panel.html.haml2
-rw-r--r--app/views/projects/_new_project_fields.html.haml2
-rw-r--r--app/views/projects/buttons/_dropdown.html.haml13
-rw-r--r--app/views/projects/clusters/gcp/_header.html.haml6
-rw-r--r--app/views/projects/clusters/show.html.haml2
-rw-r--r--app/views/projects/diffs/_file.html.haml2
-rw-r--r--app/views/projects/diffs/_file_header.html.haml3
-rw-r--r--app/views/projects/diffs/_stats.html.haml7
-rw-r--r--app/views/shared/_clone_panel.html.haml2
-rw-r--r--app/workers/check_gcp_project_billing_worker.rb6
-rw-r--r--changelogs/unreleased/36669-default-mr-title-with-external-issues.yml5
-rw-r--r--changelogs/unreleased/38068-commits-count.yml5
-rw-r--r--changelogs/unreleased/39214__pipeline_api.yml5
-rw-r--r--changelogs/unreleased/39988-hide-new-branch-tag-empty-repo.yml5
-rw-r--r--changelogs/unreleased/4020-rebase-message.yml5
-rw-r--r--changelogs/unreleased/41491-fix-nil-blob-name-error.yml5
-rw-r--r--changelogs/unreleased/41600-wider-project-readme-on-fixed-layout.yml5
-rw-r--r--changelogs/unreleased/41613-fix-redundant-modal.yml5
-rw-r--r--changelogs/unreleased/41709-rich-blob-viewer-margins-for-pc.yml5
-rw-r--r--changelogs/unreleased/41789-fix-up-web-ide-user-preference-copy-and-buttons.yml5
-rw-r--r--changelogs/unreleased/da-verify-integrity-of-uploaded-files.yml5
-rw-r--r--changelogs/unreleased/fj-41477-fix-bug-wiki-last-version.yml5
-rw-r--r--changelogs/unreleased/sh-fix-award-emoji-move-issues.yml5
-rw-r--r--changelogs/unreleased/sh-fix-bare-import-hooks.yml5
-rw-r--r--config/initializers/gollum.rb20
-rw-r--r--db/migrate/20180105212544_add_commits_count_to_merge_request_diff.rb29
-rw-r--r--db/schema.rb3
-rw-r--r--doc/administration/auth/crowd.md12
-rw-r--r--doc/administration/auth/img/crowd_application_authorisation.pngbin0 -> 126994 bytes
-rw-r--r--doc/administration/operations/fast_ssh_key_lookup.md32
-rw-r--r--doc/administration/raketasks/check.md33
-rw-r--r--doc/api/merge_requests.md24
-rw-r--r--doc/api/snippets.md12
-rw-r--r--doc/articles/how_to_install_git/index.md67
-rw-r--r--doc/articles/index.md12
-rw-r--r--doc/ci/examples/code_climate.md3
-rw-r--r--doc/development/fe_guide/style_guide_js.md37
-rw-r--r--doc/development/testing_guide/end_to_end_tests.md2
-rw-r--r--doc/topics/git/how_to_install_git/index.md66
-rw-r--r--doc/topics/git/index.md65
-rw-r--r--doc/user/permissions.md19
-rw-r--r--doc/user/project/clusters/index.md16
-rw-r--r--doc/user/project/integrations/redmine.md7
-rw-r--r--features/support/env.rb6
-rw-r--r--lib/api/merge_requests.rb16
-rw-r--r--lib/gitlab/background_migration/add_merge_request_diff_commits_count.rb26
-rw-r--r--lib/gitlab/bare_repository_import/importer.rb1
-rw-r--r--lib/gitlab/git/gitlab_projects.rb30
-rw-r--r--lib/gitlab/git/repository.rb17
-rw-r--r--lib/gitlab/gitaly_client/remote_service.rb8
-rw-r--r--lib/gitlab/shell.rb22
-rw-r--r--lib/gitlab/workhorse.rb7
-rw-r--r--lib/google_api/cloud_platform/client.rb6
-rw-r--r--lib/tasks/gitlab/uploads.rake44
-rw-r--r--package.json5
-rw-r--r--qa/README.md8
-rw-r--r--qa/qa.rb7
-rw-r--r--qa/qa/factory/base.rb41
-rw-r--r--qa/qa/factory/product.rb17
-rw-r--r--qa/qa/factory/resource/project.rb4
-rw-r--r--qa/qa/page/README.md112
-rw-r--r--qa/qa/page/admin/settings.rb7
-rw-r--r--qa/qa/page/base.rb33
-rw-r--r--qa/qa/page/dashboard/groups.rb9
-rw-r--r--qa/qa/page/dashboard/projects.rb2
-rw-r--r--qa/qa/page/element.rb32
-rw-r--r--qa/qa/page/group/new.rb11
-rw-r--r--qa/qa/page/group/show.rb7
-rw-r--r--qa/qa/page/main/login.rb12
-rw-r--r--qa/qa/page/main/oauth.rb4
-rw-r--r--qa/qa/page/mattermost/login.rb7
-rw-r--r--qa/qa/page/mattermost/main.rb7
-rw-r--r--qa/qa/page/menu/admin.rb7
-rw-r--r--qa/qa/page/menu/main.rb35
-rw-r--r--qa/qa/page/menu/side.rb8
-rw-r--r--qa/qa/page/project/new.rb13
-rw-r--r--qa/qa/page/project/settings/deploy_keys.rb7
-rw-r--r--qa/qa/page/project/settings/repository.rb7
-rw-r--r--qa/qa/page/project/show.rb17
-rw-r--r--qa/qa/page/validator.rb52
-rw-r--r--qa/qa/page/view.rb55
-rw-r--r--qa/qa/runtime/namespace.rb4
-rw-r--r--qa/qa/scenario/test/sanity/selectors.rb54
-rw-r--r--qa/qa/specs/features/project/create_spec.rb4
-rw-r--r--qa/spec/factory/base_spec.rb66
-rw-r--r--qa/spec/factory/product_spec.rb21
-rw-r--r--qa/spec/page/base_spec.rb63
-rw-r--r--qa/spec/page/element_spec.rb51
-rw-r--r--qa/spec/page/validator_spec.rb79
-rw-r--r--qa/spec/page/view_spec.rb70
-rw-r--r--qa/spec/scenario/test/sanity/selectors_spec.rb40
-rw-r--r--spec/controllers/projects/clusters/gcp_controller_spec.rb7
-rw-r--r--spec/features/projects/clusters/gcp_spec.rb36
-rw-r--r--spec/features/projects/commits/user_browses_commits_spec.rb15
-rw-r--r--spec/features/projects/tree/create_directory_spec.rb2
-rw-r--r--spec/features/projects/tree/create_file_spec.rb2
-rw-r--r--spec/features/projects/tree/upload_file_spec.rb2
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/pipelines.json4
-rw-r--r--spec/initializers/gollum_spec.rb62
-rw-r--r--spec/javascripts/boards/issue_card_spec.js6
-rw-r--r--spec/javascripts/cycle_analytics/banner_spec.js3
-rw-r--r--spec/javascripts/cycle_analytics/total_time_component_spec.js6
-rw-r--r--spec/javascripts/groups/components/app_spec.js66
-rw-r--r--spec/javascripts/groups/components/item_actions_spec.js40
-rw-r--r--spec/javascripts/oauth_remember_me_spec.js2
-rw-r--r--spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js (renamed from spec/javascripts/abuse_reports_spec.js)2
-rw-r--r--spec/javascripts/pipelines/empty_state_spec.js4
-rw-r--r--spec/javascripts/registry/components/app_spec.js2
-rw-r--r--spec/javascripts/signin_tabs_memoizer_spec.js2
-rw-r--r--spec/javascripts/vue_shared/components/clipboard_button_spec.js31
-rw-r--r--spec/javascripts/vue_shared/components/markdown/field_spec.js6
-rw-r--r--spec/javascripts/vue_shared/components/table_pagination_spec.js1
-rw-r--r--spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb50
-rw-r--r--spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb5
-rw-r--r--spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb6
-rw-r--r--spec/lib/gitlab/bare_repository_import/importer_spec.rb8
-rw-r--r--spec/lib/gitlab/git/gitlab_projects_spec.rb45
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml1
-rw-r--r--spec/lib/gitlab/shell_spec.rb34
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb14
-rw-r--r--spec/migrations/schedule_populate_merge_request_metrics_with_events_data_spec.rb6
-rw-r--r--spec/models/project_spec.rb5
-rw-r--r--spec/models/repository_spec.rb22
-rw-r--r--spec/requests/api/merge_requests_spec.rb43
-rw-r--r--spec/services/check_gcp_project_billing_service_spec.rb21
-rw-r--r--spec/services/issues/move_service_spec.rb12
-rw-r--r--spec/services/merge_requests/build_service_spec.rb26
-rw-r--r--spec/services/merge_requests/rebase_service_spec.rb14
-rw-r--r--spec/support/google_api/cloud_platform_helpers.rb47
-rw-r--r--spec/tasks/gitlab/uploads_rake_spec.rb27
-rw-r--r--spec/views/projects/buttons/_dropdown.html.haml_spec.rb39
-rw-r--r--spec/workers/check_gcp_project_billing_worker_spec.rb2
-rw-r--r--yarn.lock56
384 files changed, 8191 insertions, 5752 deletions
diff --git a/.babelrc b/.babelrc
index 2bae7ca9fbf..b93bef72de1 100644
--- a/.babelrc
+++ b/.babelrc
@@ -8,7 +8,8 @@
"plugins": [
["istanbul", {
"exclude": [
- "spec/javascripts/**/*"
+ "spec/javascripts/**/*",
+ "app/assets/javascripts/locale/**/app.js"
]
}],
["transform-define", {
diff --git a/.codeclimate.yml b/.codeclimate.yml
index d4905856e72..dc8ac60fb44 100644
--- a/.codeclimate.yml
+++ b/.codeclimate.yml
@@ -13,7 +13,8 @@ engines:
exclude_paths:
- "lib/api/v3/*"
eslint:
- enabled: true
+ # eslint-plugin-vue is locked to version 2 in codeclimate, we need version 4
+ enabled: false
rubocop:
enabled: true
channel: "gitlab-rubocop-0-52"
diff --git a/.eslintrc b/.eslintrc
index 44ad6a4896c..6dbe269e594 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -4,7 +4,10 @@
"browser": true,
"es6": true
},
- "extends": "airbnb-base",
+ "extends": [
+ "airbnb-base",
+ "plugin:vue/recommended"
+ ],
"globals": {
"__webpack_public_path__": true,
"_": false,
@@ -12,7 +15,9 @@
"gon": false,
"localStorage": false
},
- "parser": "babel-eslint",
+ "parserOptions": {
+ "parser": "babel-eslint"
+ },
"plugins": [
"filenames",
"import",
@@ -20,7 +25,7 @@
"promise"
],
"settings": {
- "html/html-extensions": [".html", ".html.raw", ".vue"],
+ "html/html-extensions": [".html", ".html.raw"],
"import/resolver": {
"webpack": {
"config": "./config/webpack.config.js"
@@ -32,6 +37,15 @@
"import/no-commonjs": "error",
"no-multiple-empty-lines": ["error", { "max": 1 }],
"promise/catch-or-return": "error",
- "no-underscore-dangle": ["error", { "allow": ["__"]}]
+ "no-underscore-dangle": ["error", { "allow": ["__"]}],
+ "vue/html-self-closing": ["error", {
+ "html": {
+ "void": "always",
+ "normal": "never",
+ "component": "always"
+ },
+ "svg": "always",
+ "math": "always"
+ }]
}
}
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 4f47d3f0171..f038ce72aeb 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -604,6 +604,7 @@ codequality:
paths: [codeclimate.json]
sast:
+ <<: *except-docs
image: registry.gitlab.com/gitlab-org/gl-sast:latest
before_script: []
script:
@@ -623,6 +624,18 @@ qa:internal:
- bundle install
- bundle exec rspec
+qa:selectors:
+ <<: *dedicated-runner
+ <<: *except-docs
+ stage: test
+ variables:
+ SETUP_DB: "false"
+ services: []
+ script:
+ - cd qa/
+ - bundle install
+ - bundle exec bin/qa Test::Sanity::Selectors
+
coverage:
<<: *dedicated-runner
<<: *except-docs-and-qa
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index e40e4fc339c..328185caaeb 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.66.0
+0.67.0
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index e030a0157c9..c68d476cc8e 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-5.10.3
+5.11.0
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index 18091983f59..1545d966571 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-3.4.0
+3.5.0
diff --git a/Gemfile b/Gemfile
index 23d0b3d9b3e..6f9fb91848d 100644
--- a/Gemfile
+++ b/Gemfile
@@ -70,6 +70,10 @@ gem 'net-ldap'
# Git Wiki
# Required manually in config/initializers/gollum.rb to control load order
gem 'gollum-lib', '~> 4.2', require: false
+
+# Before updating this gem, check if
+# https://github.com/gollum/rugged_adapter/pull/28 has been merged.
+# If it has, then remove the monkey patch for tree_entry in config/initializers/gollum.rb
gem 'gollum-rugged_adapter', '~> 0.4.4', require: false
# Language detection
@@ -402,7 +406,7 @@ group :ed25519 do
end
# Gitaly GRPC client
-gem 'gitaly-proto', '~> 0.64.0', require: 'gitaly'
+gem 'gitaly-proto', '~> 0.69.0', require: 'gitaly'
gem 'toml-rb', '~> 0.3.15', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index d10da1bd1c3..40c4f73b8a6 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -284,7 +284,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
- gitaly-proto (0.64.0)
+ gitaly-proto (0.69.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (4.7.6)
@@ -1053,7 +1053,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0)
- gitaly-proto (~> 0.64.0)
+ gitaly-proto (~> 0.69.0)
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.6.2)
diff --git a/app/assets/images/multi-editor-on.png b/app/assets/images/multi-editor-on.png
index 2bcd29abf13..d51b68da985 100644
--- a/app/assets/images/multi-editor-on.png
+++ b/app/assets/images/multi-editor-on.png
Binary files differ
diff --git a/app/assets/javascripts/blob/notebook/index.js b/app/assets/javascripts/blob/notebook/index.js
index 57b031956e8..6f1350e80fc 100644
--- a/app/assets/javascripts/blob/notebook/index.js
+++ b/app/assets/javascripts/blob/notebook/index.js
@@ -8,6 +8,9 @@ export default () => {
new Vue({
el,
+ components: {
+ notebookLab,
+ },
data() {
return {
error: false,
@@ -16,8 +19,41 @@ export default () => {
json: {},
};
},
- components: {
- notebookLab,
+ mounted() {
+ if (gon.katex_css_url) {
+ const katexStyles = document.createElement('link');
+ katexStyles.setAttribute('rel', 'stylesheet');
+ katexStyles.setAttribute('href', gon.katex_css_url);
+ document.head.appendChild(katexStyles);
+ }
+
+ if (gon.katex_js_url) {
+ const katexScript = document.createElement('script');
+ katexScript.addEventListener('load', () => {
+ this.loadFile();
+ });
+ katexScript.setAttribute('src', gon.katex_js_url);
+ document.head.appendChild(katexScript);
+ } else {
+ this.loadFile();
+ }
+ },
+ methods: {
+ loadFile() {
+ axios.get(el.dataset.endpoint)
+ .then(res => res.data)
+ .then((data) => {
+ this.json = data;
+ this.loading = false;
+ })
+ .catch((e) => {
+ if (e.status !== 200) {
+ this.loadError = true;
+ }
+
+ this.error = true;
+ });
+ },
},
template: `
<div class="container-fluid md prepend-top-default append-bottom-default">
@@ -46,41 +82,5 @@ export default () => {
</p>
</div>
`,
- methods: {
- loadFile() {
- axios.get(el.dataset.endpoint)
- .then(res => res.data)
- .then((data) => {
- this.json = data;
- this.loading = false;
- })
- .catch((e) => {
- if (e.status !== 200) {
- this.loadError = true;
- }
-
- this.error = true;
- });
- },
- },
- mounted() {
- if (gon.katex_css_url) {
- const katexStyles = document.createElement('link');
- katexStyles.setAttribute('rel', 'stylesheet');
- katexStyles.setAttribute('href', gon.katex_css_url);
- document.head.appendChild(katexStyles);
- }
-
- if (gon.katex_js_url) {
- const katexScript = document.createElement('script');
- katexScript.addEventListener('load', () => {
- this.loadFile();
- });
- katexScript.setAttribute('src', gon.katex_js_url);
- document.head.appendChild(katexScript);
- } else {
- this.loadFile();
- }
- },
});
};
diff --git a/app/assets/javascripts/blob/pdf/index.js b/app/assets/javascripts/blob/pdf/index.js
index 7109f356540..70136cc4087 100644
--- a/app/assets/javascripts/blob/pdf/index.js
+++ b/app/assets/javascripts/blob/pdf/index.js
@@ -7,6 +7,9 @@ export default () => {
return new Vue({
el,
+ components: {
+ pdfLab,
+ },
data() {
return {
error: false,
@@ -15,9 +18,6 @@ export default () => {
pdf: el.dataset.endpoint,
};
},
- components: {
- pdfLab,
- },
methods: {
onLoad() {
this.loading = false;
diff --git a/app/assets/javascripts/boards/boards_bundle.js b/app/assets/javascripts/boards/boards_bundle.js
index 679c883cdcf..90166b3d3d1 100644
--- a/app/assets/javascripts/boards/boards_bundle.js
+++ b/app/assets/javascripts/boards/boards_bundle.js
@@ -171,19 +171,14 @@ $(() => {
});
gl.IssueBoardsModalAddBtn = new Vue({
- mixins: [gl.issueBoards.ModalMixins],
el: document.getElementById('js-add-issues-btn'),
+ mixins: [gl.issueBoards.ModalMixins],
data() {
return {
modal: ModalStore.store,
store: Store.state,
};
},
- watch: {
- disabled() {
- this.updateTooltip();
- },
- },
computed: {
disabled() {
if (!this.store) {
@@ -199,6 +194,14 @@ $(() => {
return '';
},
},
+ watch: {
+ disabled() {
+ this.updateTooltip();
+ },
+ },
+ mounted() {
+ this.updateTooltip();
+ },
methods: {
updateTooltip() {
const $tooltip = $(this.$refs.addIssuesButton);
@@ -217,9 +220,6 @@ $(() => {
}
},
},
- mounted() {
- this.updateTooltip();
- },
template: `
<div class="board-extra-actions">
<button
diff --git a/app/assets/javascripts/boards/components/board_card.vue b/app/assets/javascripts/boards/components/board_card.vue
index 0b220a56e0b..23fec503586 100644
--- a/app/assets/javascripts/boards/components/board_card.vue
+++ b/app/assets/javascripts/boards/components/board_card.vue
@@ -10,12 +10,30 @@ export default {
'issue-card-inner': gl.issueBoards.IssueCardInner,
},
props: {
- list: Object,
- issue: Object,
- issueLinkBase: String,
- disabled: Boolean,
- index: Number,
- rootPath: String,
+ list: {
+ type: Object,
+ default: () => ({}),
+ },
+ issue: {
+ type: Object,
+ default: () => ({}),
+ },
+ issueLinkBase: {
+ type: String,
+ default: '',
+ },
+ disabled: {
+ type: Boolean,
+ default: false,
+ },
+ index: {
+ type: Number,
+ default: 0,
+ },
+ rootPath: {
+ type: String,
+ default: '',
+ },
},
data() {
return {
@@ -54,8 +72,13 @@ export default {
</script>
<template>
- <li class="card"
- :class="{ 'user-can-drag': !disabled && issue.id, 'is-disabled': disabled || !issue.id, 'is-active': issueDetailVisible }"
+ <li
+ class="card"
+ :class="{
+ 'user-can-drag': !disabled && issue.id,
+ 'is-disabled': disabled || !issue.id,
+ 'is-active': issueDetailVisible
+ }"
:index="index"
:data-issue-id="issue.id"
@mousedown="mouseDown"
@@ -66,6 +89,7 @@ export default {
:issue="issue"
:issue-link-base="issueLinkBase"
:root-path="rootPath"
- :update-filters="true" />
+ :update-filters="true"
+ />
</li>
</template>
diff --git a/app/assets/javascripts/clusters/components/application_row.vue b/app/assets/javascripts/clusters/components/application_row.vue
index 872abf03ef1..c13bbcee863 100644
--- a/app/assets/javascripts/clusters/components/application_row.vue
+++ b/app/assets/javascripts/clusters/components/application_row.vue
@@ -1,108 +1,112 @@
<script>
-import { s__, sprintf } from '../../locale';
-import eventHub from '../event_hub';
-import loadingButton from '../../vue_shared/components/loading_button.vue';
-import {
- APPLICATION_NOT_INSTALLABLE,
- APPLICATION_SCHEDULED,
- APPLICATION_INSTALLABLE,
- APPLICATION_INSTALLING,
- APPLICATION_INSTALLED,
- APPLICATION_ERROR,
- REQUEST_LOADING,
- REQUEST_SUCCESS,
- REQUEST_FAILURE,
-} from '../constants';
+ /* eslint-disable vue/require-default-prop */
+ import { s__, sprintf } from '../../locale';
+ import eventHub from '../event_hub';
+ import loadingButton from '../../vue_shared/components/loading_button.vue';
+ import {
+ APPLICATION_NOT_INSTALLABLE,
+ APPLICATION_SCHEDULED,
+ APPLICATION_INSTALLABLE,
+ APPLICATION_INSTALLING,
+ APPLICATION_INSTALLED,
+ APPLICATION_ERROR,
+ REQUEST_LOADING,
+ REQUEST_SUCCESS,
+ REQUEST_FAILURE,
+ } from '../constants';
-export default {
- props: {
- id: {
- type: String,
- required: true,
+ export default {
+ components: {
+ loadingButton,
},
- title: {
- type: String,
- required: true,
+ props: {
+ id: {
+ type: String,
+ required: true,
+ },
+ title: {
+ type: String,
+ required: true,
+ },
+ titleLink: {
+ type: String,
+ required: false,
+ },
+ description: {
+ type: String,
+ required: true,
+ },
+ status: {
+ type: String,
+ required: false,
+ },
+ statusReason: {
+ type: String,
+ required: false,
+ },
+ requestStatus: {
+ type: String,
+ required: false,
+ },
+ requestReason: {
+ type: String,
+ required: false,
+ },
},
- titleLink: {
- type: String,
- required: false,
- },
- description: {
- type: String,
- required: true,
- },
- status: {
- type: String,
- required: false,
- },
- statusReason: {
- type: String,
- required: false,
- },
- requestStatus: {
- type: String,
- required: false,
- },
- requestReason: {
- type: String,
- required: false,
- },
- },
- components: {
- loadingButton,
- },
- computed: {
- rowJsClass() {
- return `js-cluster-application-row-${this.id}`;
- },
- installButtonLoading() {
- return !this.status ||
- this.status === APPLICATION_SCHEDULED ||
- this.status === APPLICATION_INSTALLING ||
- this.requestStatus === REQUEST_LOADING;
- },
- installButtonDisabled() {
- // Avoid the potential for the real-time data to say APPLICATION_INSTALLABLE but
- // we already made a request to install and are just waiting for the real-time
- // to sync up.
- return (this.status !== APPLICATION_INSTALLABLE && this.status !== APPLICATION_ERROR) ||
- this.requestStatus === REQUEST_LOADING ||
- this.requestStatus === REQUEST_SUCCESS;
- },
- installButtonLabel() {
- let label;
- if (
- this.status === APPLICATION_NOT_INSTALLABLE ||
- this.status === APPLICATION_INSTALLABLE ||
- this.status === APPLICATION_ERROR
- ) {
- label = s__('ClusterIntegration|Install');
- } else if (this.status === APPLICATION_SCHEDULED || this.status === APPLICATION_INSTALLING) {
- label = s__('ClusterIntegration|Installing');
- } else if (this.status === APPLICATION_INSTALLED) {
- label = s__('ClusterIntegration|Installed');
- }
+ computed: {
+ rowJsClass() {
+ return `js-cluster-application-row-${this.id}`;
+ },
+ installButtonLoading() {
+ return !this.status ||
+ this.status === APPLICATION_SCHEDULED ||
+ this.status === APPLICATION_INSTALLING ||
+ this.requestStatus === REQUEST_LOADING;
+ },
+ installButtonDisabled() {
+ // Avoid the potential for the real-time data to say APPLICATION_INSTALLABLE but
+ // we already made a request to install and are just waiting for the real-time
+ // to sync up.
+ return (this.status !== APPLICATION_INSTALLABLE
+ && this.status !== APPLICATION_ERROR) ||
+ this.requestStatus === REQUEST_LOADING ||
+ this.requestStatus === REQUEST_SUCCESS;
+ },
+ installButtonLabel() {
+ let label;
+ if (
+ this.status === APPLICATION_NOT_INSTALLABLE ||
+ this.status === APPLICATION_INSTALLABLE ||
+ this.status === APPLICATION_ERROR
+ ) {
+ label = s__('ClusterIntegration|Install');
+ } else if (this.status === APPLICATION_SCHEDULED ||
+ this.status === APPLICATION_INSTALLING) {
+ label = s__('ClusterIntegration|Installing');
+ } else if (this.status === APPLICATION_INSTALLED) {
+ label = s__('ClusterIntegration|Installed');
+ }
- return label;
- },
- hasError() {
- return this.status === APPLICATION_ERROR || this.requestStatus === REQUEST_FAILURE;
- },
- generalErrorDescription() {
- return sprintf(
- s__('ClusterIntegration|Something went wrong while installing %{title}'), {
- title: this.title,
- },
- );
+ return label;
+ },
+ hasError() {
+ return this.status === APPLICATION_ERROR ||
+ this.requestStatus === REQUEST_FAILURE;
+ },
+ generalErrorDescription() {
+ return sprintf(
+ s__('ClusterIntegration|Something went wrong while installing %{title}'), {
+ title: this.title,
+ },
+ );
+ },
},
- },
- methods: {
- installClicked() {
- eventHub.$emit('installApplication', this.id);
+ methods: {
+ installClicked() {
+ eventHub.$emit('installApplication', this.id);
+ },
},
- },
-};
+ };
</script>
<template>
diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue
index cd58b88db69..25cef44c1b8 100644
--- a/app/assets/javascripts/clusters/components/applications.vue
+++ b/app/assets/javascripts/clusters/components/applications.vue
@@ -1,84 +1,93 @@
<script>
-import _ from 'underscore';
-import { s__, sprintf } from '../../locale';
-import applicationRow from './application_row.vue';
+ import _ from 'underscore';
+ import { s__, sprintf } from '../../locale';
+ import applicationRow from './application_row.vue';
-export default {
- props: {
- applications: {
- type: Object,
- required: false,
- default: () => ({}),
+ export default {
+ components: {
+ applicationRow,
},
- helpPath: {
- type: String,
- required: false,
+ props: {
+ applications: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ helpPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
- },
- components: {
- applicationRow,
- },
- computed: {
- generalApplicationDescription() {
- return sprintf(
- _.escape(s__('ClusterIntegration|Install applications on your cluster. Read more about %{helpLink}')), {
- helpLink: `<a href="${this.helpPath}">
- ${_.escape(s__('ClusterIntegration|installing applications'))}
- </a>`,
- },
- false,
- );
- },
- helmTillerDescription() {
- return _.escape(s__(
- `ClusterIntegration|Helm streamlines installing and managing Kubernets applications.
- Tiller runs inside of your Kubernetes Cluster, and manages
- releases of your charts.`,
- ));
- },
- ingressDescription() {
- const descriptionParagraph = _.escape(s__(
- `ClusterIntegration|Ingress gives you a way to route requests to services based on the
- request host or path, centralizing a number of services into a single entrypoint.`,
- ));
+ computed: {
+ generalApplicationDescription() {
+ return sprintf(
+ _.escape(s__(`ClusterIntegration|Install applications on your cluster.
+ Read more about %{helpLink}`)),
+ {
+ helpLink: `<a href="${this.helpPath}">
+ ${_.escape(s__('ClusterIntegration|installing applications'))}
+ </a>`,
+ },
+ false,
+ );
+ },
+ helmTillerDescription() {
+ return _.escape(s__(
+ `ClusterIntegration|Helm streamlines installing and managing Kubernets applications.
+ Tiller runs inside of your Kubernetes Cluster, and manages
+ releases of your charts.`,
+ ));
+ },
+ ingressDescription() {
+ const descriptionParagraph = _.escape(s__(
+ `ClusterIntegration|Ingress gives you a way to route requests to services based on the
+ request host or path, centralizing a number of services into a single entrypoint.`,
+ ));
- const extraCostParagraph = sprintf(
- _.escape(s__('ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which incur additional costs. See %{pricingLink}')), {
- boldNotice: `<strong>${_.escape(s__('ClusterIntegration|Note:'))}</strong>`,
- pricingLink: `<a href="https://cloud.google.com/compute/pricing#lb" target="_blank" rel="noopener noreferrer">
- ${_.escape(s__('ClusterIntegration|GKE pricing'))}
- </a>`,
- },
- false,
- );
+ const extraCostParagraph = sprintf(
+ _.escape(s__(`ClusterIntegration|%{boldNotice} This will add some
+extra resources like a load balancer,
+which incur additional costs. See %{pricingLink}`)),
+ {
+ boldNotice: `<strong>${_.escape(s__('ClusterIntegration|Note:'))}</strong>`,
+ pricingLink: `<a href="https://cloud.google.com/compute/pricing#lb" target="_blank" rel="noopener noreferrer">
+ ${_.escape(s__('ClusterIntegration|GKE pricing'))}
+ </a>`,
+ },
+ false,
+ );
- return `
- <p>
- ${descriptionParagraph}
- </p>
- <p class="append-bottom-0">
- ${extraCostParagraph}
- </p>
- `;
- },
- gitlabRunnerDescription() {
- return _.escape(s__(
- `ClusterIntegration|GitLab Runner is the open source project that is used to run your jobs
- and send the results back to GitLab.`,
- ));
- },
- prometheusDescription() {
- return sprintf(
- _.escape(s__('ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications.')), {
- gitlabIntegrationLink: `<a href="https://docs.gitlab.com/ce/user/project/integrations/prometheus.html", target="_blank" rel="noopener noreferrer">
- ${_.escape(s__('ClusterIntegration|Gitlab Integration'))}
- </a>`,
- },
- false,
- );
+ return `
+ <p>
+ ${descriptionParagraph}
+ </p>
+ <p class="append-bottom-0">
+ ${extraCostParagraph}
+ </p>
+ `;
+ },
+ gitlabRunnerDescription() {
+ return _.escape(s__(
+ `ClusterIntegration|GitLab Runner is the open source project that is used to run your jobs
+ and send the results back to GitLab.`,
+ ));
+ },
+ prometheusDescription() {
+ return sprintf(
+ _.escape(s__(`ClusterIntegration|Prometheus is an open-source monitoring system
+ with %{gitlabIntegrationLink} to monitor deployed applications.`)),
+ {
+ gitlabIntegrationLink: `<a href="https://docs.gitlab.com/ce/user/project/integrations/prometheus.html"
+target="_blank" rel="noopener noreferrer">
+ ${_.escape(s__('ClusterIntegration|Gitlab Integration'))}
+ </a>`,
+ },
+ false,
+ );
+ },
},
- },
-};
+ };
</script>
<template>
@@ -107,26 +116,29 @@ export default {
:request-reason="applications.helm.requestReason"
/>
<application-row
- id="ingress"
- :title="applications.ingress.title"
- title-link="https://kubernetes.io/docs/concepts/services-networking/ingress/"
- :description="ingressDescription"
- :status="applications.ingress.status"
- :status-reason="applications.ingress.statusReason"
- :request-status="applications.ingress.requestStatus"
- :request-reason="applications.ingress.requestReason"
- />
- <application-row
- id="prometheus"
- :title="applications.prometheus.title"
- title-link="https://prometheus.io/docs/introduction/overview/"
- :description="prometheusDescription"
- :status="applications.prometheus.status"
- :status-reason="applications.prometheus.statusReason"
- :request-status="applications.prometheus.requestStatus"
- :request-reason="applications.prometheus.requestReason"
- />
- <!-- NOTE: Don't forget to update `clusters.scss` min-height for this block and uncomment `application_spec` tests -->
+ id="ingress"
+ :title="applications.ingress.title"
+ title-link="https://kubernetes.io/docs/concepts/services-networking/ingress/"
+ :description="ingressDescription"
+ :status="applications.ingress.status"
+ :status-reason="applications.ingress.statusReason"
+ :request-status="applications.ingress.requestStatus"
+ :request-reason="applications.ingress.requestReason"
+ />
+ <application-row
+ id="prometheus"
+ :title="applications.prometheus.title"
+ title-link="https://prometheus.io/docs/introduction/overview/"
+ :description="prometheusDescription"
+ :status="applications.prometheus.status"
+ :status-reason="applications.prometheus.statusReason"
+ :request-status="applications.prometheus.requestStatus"
+ :request-reason="applications.prometheus.requestReason"
+ />
+ <!--
+ NOTE: Don't forget to update `clusters.scss`
+ min-height for this block and uncomment `application_spec` tests
+ -->
<!-- Add GitLab Runner row, all other plumbing is complete -->
</div>
</div>
diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js
index b6a0ece7907..525fbf9dac9 100644
--- a/app/assets/javascripts/commit/image_file.js
+++ b/app/assets/javascripts/commit/image_file.js
@@ -94,7 +94,7 @@ export default class ImageFile {
});
return [maxWidth, maxHeight];
}
-
+ // eslint-disable-next-line
views = {
'two-up': function() {
return $('.two-up.view .wrap', this.file).each((function(_this) {
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.vue b/app/assets/javascripts/commit/pipelines/pipelines_table.vue
index e9a0dbaa59d..da0e8063ccb 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_table.vue
+++ b/app/assets/javascripts/commit/pipelines/pipelines_table.vue
@@ -4,6 +4,10 @@
import pipelinesMixin from '../../pipelines/mixins/pipelines';
export default {
+ mixins: [
+ pipelinesMixin,
+ ],
+
props: {
endpoint: {
type: String,
@@ -31,9 +35,6 @@
default: 'child',
},
},
- mixins: [
- pipelinesMixin,
- ],
data() {
const store = new PipelineStore();
@@ -95,28 +96,29 @@
label="Loading pipelines"
size="3"
v-if="isLoading"
- />
+ />
<empty-state
v-if="shouldRenderEmptyState"
:help-page-path="helpPagePath"
:empty-state-svg-path="emptyStateSvgPath"
- />
+ />
<error-state
v-if="shouldRenderErrorState"
:error-state-svg-path="errorStateSvgPath"
- />
+ />
<div
class="table-holder"
- v-if="shouldRenderTable">
+ v-if="shouldRenderTable"
+ >
<pipelines-table-component
:pipelines="state.pipelines"
:update-graph-dropdown="updateGraphDropdown"
:auto-devops-help-path="autoDevopsHelpPath"
:view-type="viewType"
- />
+ />
</div>
</div>
</template>
diff --git a/app/assets/javascripts/cycle_analytics/components/banner.vue b/app/assets/javascripts/cycle_analytics/components/banner.vue
index 732697c134e..3204b8dd8e7 100644
--- a/app/assets/javascripts/cycle_analytics/components/banner.vue
+++ b/app/assets/javascripts/cycle_analytics/components/banner.vue
@@ -26,28 +26,34 @@
class="js-ca-dismiss-button dismiss-button"
type="button"
:aria-label="__('Dismiss Cycle Analytics introduction box')"
- @click="dismissOverviewDialog">
+ @click="dismissOverviewDialog"
+ >
<i
class="fa fa-times"
aria-hidden="true">
</i>
</button>
- <div class="svg-container" v-html="iconCycleAnalyticsSplash">
+ <div
+ class="svg-container"
+ v-html="iconCycleAnalyticsSplash"
+ >
</div>
<div class="inner-content">
<h4>
- {{__('Introducing Cycle Analytics')}}
+ {{ __('Introducing Cycle Analytics') }}
</h4>
<p>
- {{ __('Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.') }}
+ {{ __(`Cycle Analytics gives an overview
+of how much time it takes to go from idea to production in your project.`) }}
</p>
<p>
<a
:href="documentationLink"
target="_blank"
rel="nofollow"
- class="btn">
- {{__('Read more')}}
+ class="btn"
+ >
+ {{ __('Read more') }}
</a>
</p>
</div>
diff --git a/app/assets/javascripts/cycle_analytics/components/limit_warning_component.vue b/app/assets/javascripts/cycle_analytics/components/limit_warning_component.vue
index 6e94ba929b2..32ae0cc1476 100644
--- a/app/assets/javascripts/cycle_analytics/components/limit_warning_component.vue
+++ b/app/assets/javascripts/cycle_analytics/components/limit_warning_component.vue
@@ -2,25 +2,34 @@
import tooltip from '../../vue_shared/directives/tooltip';
export default {
+ directives: {
+ tooltip,
+ },
props: {
count: {
type: Number,
required: true,
},
},
- directives: {
- tooltip,
- },
};
</script>
<template>
- <span v-if="count === 50" class="events-info pull-right">
+ <span
+ v-if="count === 50"
+ class="events-info pull-right"
+ >
<i
class="fa fa-warning"
v-tooltip
aria-hidden="true"
- :title="n__('Limited to showing %d event at most', 'Limited to showing %d events at most', 50)"
- data-placement="top"></i>
+ :title="n__(
+ 'Limited to showing %d event at most',
+ 'Limited to showing %d events at most',
+ 50
+ )"
+ data-placement="top"
+ >
+ </i>
{{ n__('Showing %d event', 'Showing %d events', 50) }}
</span>
</template>
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_code_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_code_component.vue
index 45930145b0a..a71dcf78103 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_code_component.vue
+++ b/app/assets/javascripts/cycle_analytics/components/stage_code_component.vue
@@ -4,15 +4,21 @@
import totalTime from './total_time_component.vue';
export default {
- props: {
- items: Array,
- stage: Object,
- },
components: {
userAvatarImage,
limitWarning,
totalTime,
},
+ props: {
+ items: {
+ type: Array,
+ default: () => [],
+ },
+ stage: {
+ type: Object,
+ default: () => ({}),
+ },
+ },
};
</script>
<template>
@@ -22,28 +28,44 @@
<limit-warning :count="items.length" />
</div>
<ul class="stage-event-list">
- <li v-for="mergeRequest in items" class="stage-event-item">
+ <li
+ v-for="(mergeRequest, i) in items"
+ :key="i"
+ class="stage-event-item"
+ >
<div class="item-details">
<!-- FIXME: Pass an alt attribute here for accessibility -->
- <user-avatar-image :img-src="mergeRequest.author.avatarUrl"/>
+ <user-avatar-image :img-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>
+ <a
+ :href="mergeRequest.url"
+ class="issue-link">
+ !{{ mergeRequest.iid }}
+ </a>
&middot;
<span>
{{ s__('OpenedNDaysAgo|Opened') }}
- <a :href="mergeRequest.url" class="issue-date">{{ mergeRequest.createdAt }}</a>
+ <a
+ :href="mergeRequest.url"
+ class="issue-date">
+ {{ mergeRequest.createdAt }}
+ </a>
</span>
<span>
{{ s__('ByAuthor|by') }}
- <a :href="mergeRequest.author.webUrl" class="issue-author-link">{{ mergeRequest.author.name }}</a>
+ <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>
+ <total-time :time="mergeRequest.totalTime" />
</div>
</li>
</ul>
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_component.vue
index 8c98bd249a1..907638d798a 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_component.vue
+++ b/app/assets/javascripts/cycle_analytics/components/stage_component.vue
@@ -4,15 +4,21 @@
import totalTime from './total_time_component.vue';
export default {
- props: {
- items: Array,
- stage: Object,
- },
components: {
userAvatarImage,
limitWarning,
totalTime,
},
+ props: {
+ items: {
+ type: Array,
+ default: () => [],
+ },
+ stage: {
+ type: Object,
+ default: () => ({}),
+ },
+ },
};
</script>
<template>
@@ -25,30 +31,43 @@
<li
v-for="(issue, i) in items"
:key="i"
- class="stage-event-item">
+ class="stage-event-item"
+ >
<div class="item-details">
<!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image :img-src="issue.author.avatarUrl"/>
<h5 class="item-title issue-title">
- <a class="issue-title" :href="issue.url">
+ <a
+ class="issue-title"
+ :href="issue.url"
+ >
{{ issue.title }}
</a>
</h5>
- <a :href="issue.url" class="issue-link">#{{ issue.iid }}</a>
+ <a
+ :href="issue.url"
+ class="issue-link"
+ >#{{ issue.iid }}</a>
&middot;
<span>
{{ s__('OpenedNDaysAgo|Opened') }}
- <a :href="issue.url" class="issue-date">{{ issue.createdAt }}</a>
+ <a
+ :href="issue.url"
+ class="issue-date"
+ >{{ issue.createdAt }}</a>
</span>
<span>
{{ s__('ByAuthor|by') }}
- <a :href="issue.author.webUrl" class="issue-author-link">
+ <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 :time="issue.totalTime" />
</div>
</li>
</ul>
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.vue
index 75d2f1fd70c..cee294b4ac2 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.vue
+++ b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.vue
@@ -5,15 +5,21 @@
import totalTime from './total_time_component.vue';
export default {
- props: {
- items: Array,
- stage: Object,
- },
components: {
userAvatarImage,
totalTime,
limitWarning,
},
+ props: {
+ items: {
+ type: Array,
+ default: () => [],
+ },
+ stage: {
+ type: Object,
+ default: () => ({}),
+ },
+ },
computed: {
iconCommit() {
return iconCommit;
@@ -31,10 +37,11 @@
<li
v-for="(commit, i) in items"
:key="i"
- class="stage-event-item">
+ class="stage-event-item"
+ >
<div class="item-details item-conmmit-component">
<!-- FIXME: Pass an alt attribute here for accessibility -->
- <user-avatar-image :img-src="commit.author.avatarUrl"/>
+ <user-avatar-image :img-src="commit.author.avatarUrl" />
<h5 class="item-title commit-title">
<a :href="commit.commitUrl">
{{ commit.title }}
@@ -42,10 +49,20 @@
</h5>
<span>
{{ s__('FirstPushedBy|First') }}
- <span class="commit-icon" v-html="iconCommit"></span>
- <a :href="commit.commitUrl" class="commit-hash-link commit-sha">{{ commit.shortSha }}</a>
+ <span
+ class="commit-icon"
+ v-html="iconCommit"
+ >
+ </span>
+ <a
+ :href="commit.commitUrl"
+ class="commit-hash-link commit-sha"
+ >{{ commit.shortSha }}</a>
{{ s__('FirstPushedBy|pushed by') }}
- <a :href="commit.author.webUrl" class="commit-author-link">
+ <a
+ :href="commit.author.webUrl"
+ class="commit-author-link"
+ >
{{ commit.author.name }}
</a>
</span>
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_review_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_review_component.vue
index cbce9205e75..39b699a6395 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_review_component.vue
+++ b/app/assets/javascripts/cycle_analytics/components/stage_review_component.vue
@@ -5,16 +5,22 @@
import icon from '../../vue_shared/components/icon.vue';
export default {
- props: {
- items: Array,
- stage: Object,
- },
components: {
userAvatarImage,
totalTime,
limitWarning,
icon,
},
+ props: {
+ items: {
+ type: Array,
+ default: () => [],
+ },
+ stage: {
+ type: Object,
+ default: () => ({}),
+ },
+ },
};
</script>
<template>
@@ -27,7 +33,8 @@
<li
v-for="(mergeRequest, i) in items"
:key="i"
- class="stage-event-item">
+ class="stage-event-item"
+ >
<div class="item-details">
<!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image :img-src="mergeRequest.author.avatarUrl"/>
@@ -36,34 +43,52 @@
{{ mergeRequest.title }}
</a>
</h5>
- <a :href="mergeRequest.url" class="issue-link">!{{ mergeRequest.iid }}</a>
+ <a
+ :href="mergeRequest.url"
+ class="issue-link"
+ >!{{ mergeRequest.iid }}</a>
&middot;
<span>
{{ s__('OpenedNDaysAgo|Opened') }}
- <a :href="mergeRequest.url" class="issue-date">{{ mergeRequest.createdAt }}</a>
+ <a
+ :href="mergeRequest.url"
+ class="issue-date"
+ >{{ mergeRequest.createdAt }}</a>
</span>
<span>
{{ s__('ByAuthor|by') }}
- <a :href="mergeRequest.author.webUrl" class="issue-author-link">{{ mergeRequest.author.name }}</a>
+ <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>
+ <i
+ class="fa fa-ban"
+ aria-hidden="true"
+ >
+ </i>
{{ mergeRequest.state.toUpperCase() }}
</span>
</template>
<template v-else>
- <span class="merge-request-branch" v-if="mergeRequest.branch">
+ <span
+ class="merge-request-branch"
+ v-if="mergeRequest.branch"
+ >
<icon
name="fork"
- :size="16">
- </icon>
- <a :href="mergeRequest.branch.url">{{ mergeRequest.branch.name }}</a>
+ :size="16"
+ />
+ <a :href="mergeRequest.branch.url">
+ {{ mergeRequest.branch.name }}
+ </a>
</span>
</template>
</div>
<div class="item-time">
- <total-time :time="mergeRequest.totalTime"/>
+ <total-time :time="mergeRequest.totalTime" />
</div>
</li>
</ul>
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue
index 508a411e599..92f2a95a66a 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue
+++ b/app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue
@@ -6,16 +6,22 @@
import icon from '../../vue_shared/components/icon.vue';
export default {
- props: {
- items: Array,
- stage: Object,
- },
components: {
userAvatarImage,
totalTime,
limitWarning,
icon,
},
+ props: {
+ items: {
+ type: Array,
+ default: () => [],
+ },
+ stage: {
+ type: Object,
+ default: () => ({}),
+ },
+ },
computed: {
iconBranch() {
return iconBranch;
@@ -33,30 +39,58 @@
<li
v-for="(build, i) in items"
class="stage-event-item item-build-component"
- :key="i">
+ :key="i"
+ >
<div class="item-details">
<!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image :img-src="build.author.avatarUrl"/>
<h5 class="item-title">
- <a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
+ <a
+ :href="build.url"
+ class="pipeline-id"
+ >
+ #{{ build.id }}
+ </a>
<icon
name="fork"
- :size="16">
- </icon>
- <a :href="build.branch.url" class="ref-name">{{ build.branch.name }}</a>
- <span class="icon-branch" v-html="iconBranch"></span>
- <a :href="build.commitUrl" class="commit-sha">{{ build.shortSha }}</a>
+ :size="16"
+ />
+ <a
+ :href="build.branch.url"
+ class="ref-name"
+ >
+ {{ build.branch.name }}
+ </a>
+ <span
+ class="icon-branch"
+ v-html="iconBranch"
+ >
+ </span>
+ <a
+ :href="build.commitUrl"
+ class="commit-sha"
+ >
+ {{ build.shortSha }}
+ </a>
</h5>
<span>
- <a :href="build.url" class="build-date">{{ build.date }}</a>
+ <a
+ :href="build.url"
+ class="build-date"
+ >
+ {{ build.date }}
+ </a>
{{ s__('ByAuthor|by') }}
- <a :href="build.author.webUrl" class="issue-author-link">
+ <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 :time="build.totalTime" />
</div>
</li>
</ul>
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_test_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_test_component.vue
index 88fa6b073ca..b84bb6ed792 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_test_component.vue
+++ b/app/assets/javascripts/cycle_analytics/components/stage_test_component.vue
@@ -6,15 +6,21 @@
import icon from '../../vue_shared/components/icon.vue';
export default {
- props: {
- items: Array,
- stage: Object,
- },
components: {
totalTime,
limitWarning,
icon,
},
+ props: {
+ items: {
+ type: Array,
+ default: () => [],
+ },
+ stage: {
+ type: Object,
+ default: () => ({}),
+ },
+ },
computed: {
iconBuildStatus() {
return iconBuildStatus;
@@ -35,29 +41,59 @@
<li
v-for="(build, i) in items"
:key="i"
- class="stage-event-item item-build-component">
+ class="stage-event-item item-build-component"
+ >
<div class="item-details">
<h5 class="item-title">
- <span class="icon-build-status" v-html="iconBuildStatus"></span>
- <a :href="build.url" class="item-build-name">{{ build.name }}</a>
+ <span
+ class="icon-build-status"
+ v-html="iconBuildStatus"
+ >
+ </span>
+ <a
+ :href="build.url"
+ class="item-build-name"
+ >
+ {{ build.name }}
+ </a>
&middot;
- <a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
+ <a
+ :href="build.url"
+ class="pipeline-id"
+ >
+ #{{ build.id }}
+ </a>
<icon
name="fork"
- :size="16">
- </icon>
- <a :href="build.branch.url" class="ref-name">{{ build.branch.name }}</a>
- <span class="icon-branch" v-html="iconBranch"></span>
- <a :href="build.commitUrl" class="commit-sha">{{ build.shortSha }}</a>
+ :size="16"
+ />
+ <a
+ :href="build.branch.url"
+ class="ref-name"
+ >
+ {{ build.branch.name }}
+ </a>
+ <span
+ class="icon-branch"
+ v-html="iconBranch"
+ >
+ </span>
+ <a
+ :href="build.commitUrl"
+ class="commit-sha">
+ {{ build.shortSha }}
+ </a>
</h5>
<span>
- <a :href="build.url" class="issue-date">
+ <a
+ :href="build.url"
+ class="issue-date">
{{ build.date }}
</a>
</span>
</div>
<div class="item-time">
- <total-time :time="build.totalTime"/>
+ <total-time :time="build.totalTime" />
</div>
</li>
</ul>
diff --git a/app/assets/javascripts/cycle_analytics/components/total_time_component.vue b/app/assets/javascripts/cycle_analytics/components/total_time_component.vue
index 62efd4f9c28..7758bf0cb3f 100644
--- a/app/assets/javascripts/cycle_analytics/components/total_time_component.vue
+++ b/app/assets/javascripts/cycle_analytics/components/total_time_component.vue
@@ -17,13 +17,33 @@
<template>
<span class="total-time">
<template v-if="hasData">
- <template v-if="time.days">{{ time.days }} <span>{{ n__('day', 'days', time.days) }}</span></template>
- <template v-if="time.hours">{{ time.hours }} <span>{{ n__('Time|hr', 'Time|hrs', time.hours) }}</span></template>
- <template v-if="time.mins && !time.days">{{ time.mins }} <span>{{ n__('Time|min', 'Time|mins', time.mins) }}</span></template>
- <template v-if="time.seconds && hasData === 1 || time.seconds === 0">{{ time.seconds }} <span>{{ s__('Time|s') }}</span></template>
+ <template v-if="time.days">
+ {{ time.days }}
+ <span>
+ {{ n__('day', 'days', time.days) }}
+ </span>
+ </template>
+ <template v-if="time.hours">
+ {{ time.hours }}
+ <span>
+ {{ n__('Time|hr', 'Time|hrs', time.hours) }}
+ </span>
+ </template>
+ <template v-if="time.mins && !time.days">
+ {{ time.mins }}
+ <span>
+ {{ n__('Time|min', 'Time|mins', time.mins) }}
+ </span>
+ </template>
+ <template v-if="time.seconds && hasData === 1 || time.seconds === 0">
+ {{ time.seconds }}
+ <span>
+ {{ s__('Time|s') }}
+ </span>
+ </template>
</template>
<template v-else>
--
</template>
- </span>
+ </span>
</template>
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
index 49bb6c52180..034f2923b3b 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
@@ -20,6 +20,16 @@ $(() => {
gl.cycleAnalyticsApp = new Vue({
el: '#cycle-analytics',
name: 'CycleAnalytics',
+ components: {
+ banner,
+ 'stage-issue-component': stageComponent,
+ 'stage-plan-component': stagePlanComponent,
+ 'stage-code-component': stageCodeComponent,
+ 'stage-test-component': stageTestComponent,
+ 'stage-review-component': stageReviewComponent,
+ 'stage-staging-component': stageStagingComponent,
+ 'stage-production-component': stageComponent,
+ },
data() {
const cycleAnalyticsEl = document.querySelector('#cycle-analytics');
const cycleAnalyticsService = new CycleAnalyticsService({
@@ -43,16 +53,6 @@ $(() => {
return this.store.currentActiveStage();
},
},
- components: {
- banner,
- 'stage-issue-component': stageComponent,
- 'stage-plan-component': stagePlanComponent,
- 'stage-code-component': stageCodeComponent,
- 'stage-test-component': stageTestComponent,
- 'stage-review-component': stageReviewComponent,
- 'stage-staging-component': stageStagingComponent,
- 'stage-production-component': stageComponent,
- },
created() {
this.fetchCycleAnalyticsData();
},
diff --git a/app/assets/javascripts/deploy_keys/components/action_btn.vue b/app/assets/javascripts/deploy_keys/components/action_btn.vue
index f9f2f9bf693..b839b9f286f 100644
--- a/app/assets/javascripts/deploy_keys/components/action_btn.vue
+++ b/app/assets/javascripts/deploy_keys/components/action_btn.vue
@@ -3,10 +3,8 @@
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
export default {
- data() {
- return {
- isLoading: false,
- };
+ components: {
+ loadingIcon,
},
props: {
deployKey: {
@@ -23,11 +21,16 @@
default: 'btn-default',
},
},
-
- components: {
- loadingIcon,
+ data() {
+ return {
+ isLoading: false,
+ };
+ },
+ computed: {
+ text() {
+ return `${this.type.charAt(0).toUpperCase()}${this.type.slice(1)}`;
+ },
},
-
methods: {
doAction() {
this.isLoading = true;
@@ -37,11 +40,6 @@
});
},
},
- computed: {
- text() {
- return `${this.type.charAt(0).toUpperCase()}${this.type.slice(1)}`;
- },
- },
};
</script>
diff --git a/app/assets/javascripts/deploy_keys/components/app.vue b/app/assets/javascripts/deploy_keys/components/app.vue
index fe046449054..7b68b19de75 100644
--- a/app/assets/javascripts/deploy_keys/components/app.vue
+++ b/app/assets/javascripts/deploy_keys/components/app.vue
@@ -7,11 +7,9 @@
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
export default {
- data() {
- return {
- isLoading: false,
- store: new DeployKeysStore(),
- };
+ components: {
+ keysPanel,
+ loadingIcon,
},
props: {
endpoint: {
@@ -19,6 +17,12 @@
required: true,
},
},
+ data() {
+ return {
+ isLoading: false,
+ store: new DeployKeysStore(),
+ };
+ },
computed: {
hasKeys() {
return Object.keys(this.keys).length;
@@ -27,9 +31,20 @@
return this.store.keys;
},
},
- components: {
- keysPanel,
- loadingIcon,
+ created() {
+ this.service = new DeployKeysService(this.endpoint);
+
+ eventHub.$on('enable.key', this.enableKey);
+ eventHub.$on('remove.key', this.disableKey);
+ eventHub.$on('disable.key', this.disableKey);
+ },
+ mounted() {
+ this.fetchKeys();
+ },
+ beforeDestroy() {
+ eventHub.$off('enable.key', this.enableKey);
+ eventHub.$off('remove.key', this.disableKey);
+ eventHub.$off('disable.key', this.disableKey);
},
methods: {
fetchKeys() {
@@ -59,21 +74,6 @@
}
},
},
- created() {
- this.service = new DeployKeysService(this.endpoint);
-
- eventHub.$on('enable.key', this.enableKey);
- eventHub.$on('remove.key', this.disableKey);
- eventHub.$on('disable.key', this.disableKey);
- },
- mounted() {
- this.fetchKeys();
- },
- beforeDestroy() {
- eventHub.$off('enable.key', this.enableKey);
- eventHub.$off('remove.key', this.disableKey);
- eventHub.$off('disable.key', this.disableKey);
- },
};
</script>
diff --git a/app/assets/javascripts/deploy_keys/components/key.vue b/app/assets/javascripts/deploy_keys/components/key.vue
index 2a05c6f001e..a9e819b8a3c 100644
--- a/app/assets/javascripts/deploy_keys/components/key.vue
+++ b/app/assets/javascripts/deploy_keys/components/key.vue
@@ -3,6 +3,9 @@
import { getTimeago } from '../../lib/utils/datetime_utility';
export default {
+ components: {
+ actionBtn,
+ },
props: {
deployKey: {
type: Object,
@@ -17,9 +20,6 @@
required: true,
},
},
- components: {
- actionBtn,
- },
computed: {
timeagoDate() {
return getTimeago().format(this.deployKey.created_at);
@@ -61,9 +61,10 @@
</div>
<div class="deploy-key-content prepend-left-default deploy-key-projects">
<a
- v-for="project in deployKey.projects"
+ v-for="(project, i) in deployKey.projects"
class="label deploy-project-label"
:href="project.full_path"
+ :key="i"
>
{{ project.full_name }}
</a>
diff --git a/app/assets/javascripts/deploy_keys/components/keys_panel.vue b/app/assets/javascripts/deploy_keys/components/keys_panel.vue
index 9e6fb244af6..822b0323156 100644
--- a/app/assets/javascripts/deploy_keys/components/keys_panel.vue
+++ b/app/assets/javascripts/deploy_keys/components/keys_panel.vue
@@ -2,6 +2,9 @@
import key from './key.vue';
export default {
+ components: {
+ key,
+ },
props: {
title: {
type: String,
@@ -25,9 +28,6 @@
required: true,
},
},
- components: {
- key,
- },
};
</script>
@@ -37,12 +37,14 @@
{{ title }}
({{ keys.length }})
</h5>
- <ul class="well-list"
+ <ul
+ class="well-list"
v-if="keys.length"
>
<li
v-for="deployKey in keys"
- :key="deployKey.id">
+ :key="deployKey.id"
+ >
<key
:deploy-key="deployKey"
:store="store"
diff --git a/app/assets/javascripts/deploy_keys/index.js b/app/assets/javascripts/deploy_keys/index.js
index a5f232f950a..ca8798facc9 100644
--- a/app/assets/javascripts/deploy_keys/index.js
+++ b/app/assets/javascripts/deploy_keys/index.js
@@ -3,14 +3,14 @@ import deployKeysApp from './components/app.vue';
document.addEventListener('DOMContentLoaded', () => new Vue({
el: document.getElementById('js-deploy-keys'),
+ components: {
+ deployKeysApp,
+ },
data() {
return {
endpoint: this.$options.el.dataset.endpoint,
};
},
- components: {
- deployKeysApp,
- },
render(createElement) {
return createElement('deploy-keys-app', {
props: {
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 206f5bfaa6e..a53f690d703 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -12,12 +12,7 @@ import notificationsDropdown from './notifications_dropdown';
import groupAvatar from './group_avatar';
import GroupLabelSubscription from './group_label_subscription';
import LineHighlighter from './line_highlighter';
-import BuildArtifacts from './build_artifacts';
-import CILintEditor from './ci_lint_editor';
import groupsSelect from './groups_select';
-import Search from './search';
-import initAdmin from './admin';
-import NamespaceSelect from './namespace_select';
import NewCommitForm from './new_commit_form';
import Project from './project';
import projectAvatar from './project_avatar';
@@ -38,12 +33,10 @@ import BindInOut from './behaviors/bind_in_out';
import SecretValues from './behaviors/secret_values';
import DeleteModal from './branches/branches_delete_modal';
import Group from './group';
-import GroupsList from './groups_list';
import ProjectsList from './projects_list';
import setupProjectEdit from './project_edit';
import MiniPipelineGraph from './mini_pipeline_graph_dropdown';
import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater';
-import Landing from './landing';
import BlobForkSuggestion from './blob/blob_fork_suggestion';
import UserCallout from './user_callout';
import ShortcutsWiki from './shortcuts_wiki';
@@ -54,17 +47,12 @@ import UsersSelect from './users_select';
import RefSelectDropdown from './ref_select_dropdown';
import GfmAutoComplete from './gfm_auto_complete';
import ShortcutsBlob from './shortcuts_blob';
-import SigninTabsMemoizer from './signin_tabs_memoizer';
import Star from './star';
import TreeView from './tree';
-import UsagePing from './usage_ping';
-import UsernameValidator from './username_validator';
import Wikis from './wikis';
import ZenMode from './zen_mode';
import initSettingsPanels from './settings_panels';
-import OAuthRememberMe from './oauth_remember_me';
import PerformanceBar from './performance_bar';
-import initBroadcastMessagesForm from './broadcast_message';
import initNotes from './init_notes';
import initLegacyFilters from './init_legacy_filters';
import initIssuableSidebar from './init_issuable_sidebar';
@@ -72,7 +60,6 @@ import initProjectVisibilitySelector from './project_visibility';
import GpgBadges from './gpg_badges';
import initChangesDropdown from './init_changes_dropdown';
import NewGroupChild from './groups/new_group_child';
-import AbuseReports from './abuse_reports';
import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils';
import AjaxLoadingSpinner from './ajax_loading_spinner';
import GlFieldErrors from './gl_field_errors';
@@ -157,9 +144,9 @@ import Activities from './activities';
switch (page) {
case 'sessions:new':
- new UsernameValidator();
- new SigninTabsMemoizer();
- new OAuthRememberMe({ container: $(".omniauth-container") }).bindEvents();
+ import('./pages/sessions/new')
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:boards:show':
case 'projects:boards:index':
@@ -185,15 +172,25 @@ import Activities from './activities';
initIssuableSidebar();
break;
case 'dashboard:milestones:index':
- projectSelect();
+ import('./pages/dashboard/milestones/index')
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:milestones:show':
case 'groups:milestones:show':
- case 'dashboard:milestones:show':
new Milestone();
new Sidebar();
break;
+ case 'dashboard:milestones:show':
+ import('./pages/dashboard/milestones/show')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'dashboard:issues':
+ import('./pages/dashboard/issues')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'dashboard:merge_requests':
projectSelect();
initLegacyFilters();
@@ -211,22 +208,21 @@ import Activities from './activities';
break;
case 'dashboard:projects:index':
case 'dashboard:projects:starred':
+ import('./pages/dashboard/projects')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'explore:projects:index':
case 'explore:projects:trending':
case 'explore:projects:starred':
- case 'admin:projects:index':
- new ProjectsList();
+ import('./pages/explore/projects')
+ .then(callDefault)
+ .catch(fail);
break;
case 'explore:groups:index':
- new GroupsList();
- const landingElement = document.querySelector('.js-explore-groups-landing');
- if (!landingElement) break;
- const exploreGroupsLanding = new Landing(
- landingElement,
- landingElement.querySelector('.dismiss-button'),
- 'explore_groups_landing_dismissed',
- );
- exploreGroupsLanding.toggle();
+ import('./pages/explore/groups')
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:milestones:new':
case 'projects:milestones:edit':
@@ -331,7 +327,9 @@ import Activities from './activities';
shortcut_handler = new ShortcutsIssuable(true);
break;
case 'dashboard:activity':
- new Activities();
+ import('./pages/dashboard/activity')
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:commit:show':
new Diff();
@@ -352,8 +350,10 @@ import Activities from './activities';
$('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath);
break;
case 'projects:activity':
- new Activities();
- shortcut_handler = new ShortcutsNavigation();
+ import('./pages/projects/activity')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
break;
case 'projects:commits:show':
CommitsList.init(document.querySelector('.js-project-commits-show').dataset.commitsLimit);
@@ -430,15 +430,23 @@ import Activities from './activities';
new UsersSelect();
break;
case 'groups:new':
- case 'admin:groups:new':
case 'groups:create':
- case 'admin:groups:create':
BindInOut.initAll();
new Group();
groupAvatar();
break;
- case 'groups:edit':
+ case 'admin:groups:create':
+ case 'admin:groups:new':
+ import('./pages/admin/groups/new')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'admin:groups:edit':
+ import('./pages/admin/groups/edit')
+ .then(callDefault)
+ .catch(fail);
+ break;
+ case 'groups:edit':
groupAvatar();
break;
case 'projects:tree:show':
@@ -495,16 +503,20 @@ import Activities from './activities';
break;
case 'projects:forks:new':
import(/* webpackChunkName: 'project_fork' */ './project_fork')
- .then(fork => fork.default())
- .catch(() => {});
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:artifacts:browse':
- new ShortcutsNavigation();
- new BuildArtifacts();
+ import('./pages/projects/artifacts/browse')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
break;
case 'projects:artifacts:file':
- new ShortcutsNavigation();
- new BlobViewer();
+ import('./pages/projects/artifacts/file')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
break;
case 'help:index':
import('./pages/help')
@@ -512,7 +524,9 @@ import Activities from './activities';
.catch(fail);
break;
case 'search:show':
- new Search();
+ import('./pages/search/show')
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:settings:repository:show':
// Initialize expandable settings panels
@@ -536,22 +550,19 @@ import Activities from './activities';
break;
case 'ci:lints:create':
case 'ci:lints:show':
- new CILintEditor();
+ import('./pages/ci/lints').then(m => m.default()).catch(fail);
break;
case 'users:show':
import('./pages/users/show').then(callDefault).catch(fail);
break;
case 'admin:conversational_development_index:show':
- new UserCallout();
+ import('./pages/admin/conversational_development_index/show').then(m => m.default()).catch(fail);
break;
case 'snippets:show':
- new LineHighlighter();
- new BlobViewer();
- initNotes();
- new ZenMode();
+ import('./pages/snippets/show').then(m => m.default()).catch(fail);
break;
case 'import:fogbugz:new_user_map':
- new UsersSelect();
+ import('./pages/import/fogbugz/new_user_map').then(m => m.default()).catch(fail);
break;
case 'profiles:personal_access_tokens:index':
import('./pages/profiles/personal_access_tokens')
@@ -559,7 +570,9 @@ import Activities from './activities';
.catch(fail);
break;
case 'admin:impersonation_tokens:index':
- new DueDateSelectors();
+ import('./pages/admin/impersonation_tokens')
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:clusters:show':
import(/* webpackChunkName: "clusters" */ './clusters/clusters_bundle')
@@ -593,29 +606,51 @@ import Activities from './activities';
// needed in rspec
gl.u2fAuthenticate = u2fAuthenticate;
case 'admin':
- initAdmin();
+ import('./pages/admin')
+ .then(callDefault)
+ .catch(fail);
switch (path[1]) {
case 'broadcast_messages':
- initBroadcastMessagesForm();
+ import('./pages/admin/broadcast_messages')
+ .then(callDefault)
+ .catch(fail);
break;
case 'cohorts':
- new UsagePing();
+ import('./pages/admin/cohorts')
+ .then(callDefault)
+ .catch(fail);
break;
case 'groups':
- new UsersSelect();
+ switch (path[2]) {
+ case 'show':
+ import('./pages/admin/groups/show')
+ .then(callDefault)
+ .catch(fail);
+ break;
+ }
break;
case 'projects':
- document.querySelectorAll('.js-namespace-select')
- .forEach(dropdown => new NamespaceSelect({ dropdown }));
+ import('./pages/admin/projects')
+ .then(callDefault)
+ .catch(fail);
break;
case 'labels':
switch (path[2]) {
case 'new':
+ import('./pages/admin/labels/new')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'edit':
- new Labels();
+ import('./pages/admin/labels/edit')
+ .then(callDefault)
+ .catch(fail);
+ break;
}
case 'abuse_reports':
- new AbuseReports();
+ import('./pages/admin/abuse_reports')
+ .then(callDefault)
+ .catch(fail);
break;
}
break;
@@ -639,8 +674,8 @@ import Activities from './activities';
shortcut_handler = new ShortcutsNavigation();
new ProjectNew();
import(/* webpackChunkName: 'project_permissions' */ './projects/permissions')
- .then(permissions => permissions.default())
- .catch(() => {});
+ .then(callDefault)
+ .catch(fail);
break;
case 'new':
new ProjectNew();
diff --git a/app/assets/javascripts/environments/components/container.vue b/app/assets/javascripts/environments/components/container.vue
index 3236077c3cf..dbee81fa320 100644
--- a/app/assets/javascripts/environments/components/container.vue
+++ b/app/assets/javascripts/environments/components/container.vue
@@ -4,6 +4,11 @@
import environmentTable from '../components/environments_table.vue';
export default {
+ components: {
+ environmentTable,
+ loadingIcon,
+ tablePagination,
+ },
props: {
isLoading: {
type: Boolean,
@@ -26,12 +31,6 @@
required: true,
},
},
- components: {
- environmentTable,
- loadingIcon,
- tablePagination,
- },
-
methods: {
onChangePage(page) {
this.$emit('onChangePage', page);
@@ -47,7 +46,7 @@
label="Loading environments"
v-if="isLoading"
size="3"
- />
+ />
<slot name="emptyState"></slot>
@@ -59,13 +58,13 @@
:environments="environments"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
- />
+ />
<table-pagination
v-if="pagination && pagination.totalPages > 1"
:change="onChangePage"
- :pageInfo="pagination"
- />
+ :page-info="pagination"
+ />
</div>
</div>
</template>
diff --git a/app/assets/javascripts/environments/components/empty_state.vue b/app/assets/javascripts/environments/components/empty_state.vue
index 2646f08c8e6..00e63c3467a 100644
--- a/app/assets/javascripts/environments/components/empty_state.vue
+++ b/app/assets/javascripts/environments/components/empty_state.vue
@@ -1,6 +1,6 @@
<script>
export default {
- name: 'environmentsEmptyState',
+ name: 'EnvironmentsEmptyState',
props: {
newPath: {
type: String,
@@ -21,21 +21,23 @@
<div class="blank-state-row">
<div class="blank-state-center">
<h2 class="blank-state-title js-blank-state-title">
- {{s__("Environments|You don't have any environments right now.")}}
+ {{ s__("Environments|You don't have any environments right now.") }}
</h2>
<p class="blank-state-text">
- {{s__("Environments|Environments are places where code gets deployed, such as staging or production.")}}
+ {{ s__(`Environments|Environments are places where
+code gets deployed, such as staging or production.`) }}
<br />
<a :href="helpPath">
- {{s__("Environments|Read more about environments")}}
+ {{ s__("Environments|Read more about environments") }}
</a>
</p>
<a
v-if="canCreateEnvironment"
:href="newPath"
- class="btn btn-create js-new-environment-button">
- {{s__("Environments|New environment")}}
+ class="btn btn-create js-new-environment-button"
+ >
+ {{ s__("Environments|New environment") }}
</a>
</div>
</div>
diff --git a/app/assets/javascripts/environments/components/environment_actions.vue b/app/assets/javascripts/environments/components/environment_actions.vue
index e7495677e7c..16bd2f5feb3 100644
--- a/app/assets/javascripts/environments/components/environment_actions.vue
+++ b/app/assets/javascripts/environments/components/environment_actions.vue
@@ -1,55 +1,54 @@
<script>
-import playIconSvg from 'icons/_icon_play.svg';
-import eventHub from '../event_hub';
-import loadingIcon from '../../vue_shared/components/loading_icon.vue';
-import tooltip from '../../vue_shared/directives/tooltip';
+ import playIconSvg from 'icons/_icon_play.svg';
+ import eventHub from '../event_hub';
+ import loadingIcon from '../../vue_shared/components/loading_icon.vue';
+ import tooltip from '../../vue_shared/directives/tooltip';
-export default {
- props: {
- actions: {
- type: Array,
- required: false,
- default: () => [],
+ export default {
+ directives: {
+ tooltip,
},
- },
- directives: {
- tooltip,
- },
-
- components: {
- loadingIcon,
- },
+ components: {
+ loadingIcon,
+ },
+ props: {
+ actions: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ },
- data() {
- return {
- playIconSvg,
- isLoading: false,
- };
- },
+ data() {
+ return {
+ playIconSvg,
+ isLoading: false,
+ };
+ },
- computed: {
- title() {
- return 'Deploy to...';
+ computed: {
+ title() {
+ return 'Deploy to...';
+ },
},
- },
- methods: {
- onClickAction(endpoint) {
- this.isLoading = true;
+ methods: {
+ onClickAction(endpoint) {
+ this.isLoading = true;
- eventHub.$emit('postAction', endpoint);
- },
+ eventHub.$emit('postAction', endpoint);
+ },
- isActionDisabled(action) {
- if (action.playable === undefined) {
- return false;
- }
+ isActionDisabled(action) {
+ if (action.playable === undefined) {
+ return false;
+ }
- return !action.playable;
+ return !action.playable;
+ },
},
- },
-};
+ };
</script>
<template>
<div
@@ -63,27 +62,33 @@ export default {
data-toggle="dropdown"
:title="title"
:aria-label="title"
- :disabled="isLoading">
+ :disabled="isLoading"
+ >
<span>
<span v-html="playIconSvg"></span>
<i
class="fa fa-caret-down"
- aria-hidden="true"/>
+ aria-hidden="true"
+ >
+ </i>
<loading-icon v-if="isLoading" />
</span>
</button>
<ul class="dropdown-menu dropdown-menu-align-right">
- <li v-for="action in actions">
+ <li
+ v-for="(action, i) in actions"
+ :key="i">
<button
type="button"
class="js-manual-action-link no-btn btn"
@click="onClickAction(action.play_path)"
:class="{ disabled: isActionDisabled(action) }"
- :disabled="isActionDisabled(action)">
+ :disabled="isActionDisabled(action)"
+ >
<span v-html="playIconSvg"></span>
<span>
- {{action.name}}
+ {{ action.name }}
</span>
</button>
</li>
diff --git a/app/assets/javascripts/environments/components/environment_external_url.vue b/app/assets/javascripts/environments/components/environment_external_url.vue
index 520c3ac8ace..c9a68cface6 100644
--- a/app/assets/javascripts/environments/components/environment_external_url.vue
+++ b/app/assets/javascripts/environments/components/environment_external_url.vue
@@ -1,28 +1,27 @@
<script>
-import tooltip from '../../vue_shared/directives/tooltip';
-import { s__ } from '../../locale';
+ import tooltip from '../../vue_shared/directives/tooltip';
+ import { s__ } from '../../locale';
-/**
- * Renders the external url link in environments table.
- */
-export default {
- props: {
- externalUrl: {
- type: String,
- required: true,
+ /**
+ * Renders the external url link in environments table.
+ */
+ export default {
+ directives: {
+ tooltip,
+ },
+ props: {
+ externalUrl: {
+ type: String,
+ required: true,
+ },
},
- },
-
- directives: {
- tooltip,
- },
- computed: {
- title() {
- return s__('Environments|Open');
+ computed: {
+ title() {
+ return s__('Environments|Open');
+ },
},
- },
-};
+ };
</script>
<template>
<a
@@ -33,9 +32,12 @@ export default {
rel="noopener noreferrer nofollow"
:title="title"
:aria-label="title"
- :href="externalUrl">
+ :href="externalUrl"
+ >
<i
class="fa fa-external-link"
- aria-hidden="true" />
+ aria-hidden="true"
+ >
+ </i>
</a>
</template>
diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue
index 2f0e397aa45..a9d554e549e 100644
--- a/app/assets/javascripts/environments/components/environment_item.vue
+++ b/app/assets/javascripts/environments/components/environment_item.vue
@@ -1,423 +1,424 @@
<script>
-import Timeago from 'timeago.js';
-import _ from 'underscore';
-import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
-import { humanize } from '../../lib/utils/text_utility';
-import ActionsComponent from './environment_actions.vue';
-import ExternalUrlComponent from './environment_external_url.vue';
-import StopComponent from './environment_stop.vue';
-import RollbackComponent from './environment_rollback.vue';
-import TerminalButtonComponent from './environment_terminal_button.vue';
-import MonitoringButtonComponent from './environment_monitoring.vue';
-import CommitComponent from '../../vue_shared/components/commit.vue';
-import eventHub from '../event_hub';
-
-/**
- * Envrionment Item Component
- *
- * Renders a table row for each environment.
- */
-const timeagoInstance = new Timeago();
-
-export default {
- components: {
- userAvatarLink,
- 'commit-component': CommitComponent,
- 'actions-component': ActionsComponent,
- 'external-url-component': ExternalUrlComponent,
- 'stop-component': StopComponent,
- 'rollback-component': RollbackComponent,
- 'terminal-button-component': TerminalButtonComponent,
- 'monitoring-button-component': MonitoringButtonComponent,
- },
-
- props: {
- model: {
- type: Object,
- required: true,
- default: () => ({}),
+ import Timeago from 'timeago.js';
+ import _ from 'underscore';
+ import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
+ import { humanize } from '../../lib/utils/text_utility';
+ import ActionsComponent from './environment_actions.vue';
+ import ExternalUrlComponent from './environment_external_url.vue';
+ import StopComponent from './environment_stop.vue';
+ import RollbackComponent from './environment_rollback.vue';
+ import TerminalButtonComponent from './environment_terminal_button.vue';
+ import MonitoringButtonComponent from './environment_monitoring.vue';
+ import CommitComponent from '../../vue_shared/components/commit.vue';
+ import eventHub from '../event_hub';
+
+ /**
+ * Envrionment Item Component
+ *
+ * Renders a table row for each environment.
+ */
+ const timeagoInstance = new Timeago();
+
+ export default {
+ components: {
+ userAvatarLink,
+ 'commit-component': CommitComponent,
+ 'actions-component': ActionsComponent,
+ 'external-url-component': ExternalUrlComponent,
+ 'stop-component': StopComponent,
+ 'rollback-component': RollbackComponent,
+ 'terminal-button-component': TerminalButtonComponent,
+ 'monitoring-button-component': MonitoringButtonComponent,
},
- canCreateDeployment: {
- type: Boolean,
- required: false,
- default: false,
+ props: {
+ model: {
+ type: Object,
+ required: true,
+ default: () => ({}),
+ },
+
+ canCreateDeployment: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+
+ canReadEnvironment: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
- canReadEnvironment: {
- type: Boolean,
- required: false,
- default: false,
- },
- },
-
- computed: {
- /**
- * Verifies if `last_deployment` key exists in the current Envrionment.
- * This key is required to render most of the html - this method works has
- * an helper.
- *
- * @returns {Boolean}
- */
- hasLastDeploymentKey() {
- if (this.model &&
- this.model.last_deployment &&
- !_.isEmpty(this.model.last_deployment)) {
- return true;
- }
- return false;
- },
-
- /**
- * Verifies is the given environment has manual actions.
- * Used to verify if we should render them or nor.
- *
- * @returns {Boolean|Undefined}
- */
- hasManualActions() {
- return this.model &&
- this.model.last_deployment &&
- this.model.last_deployment.manual_actions &&
- this.model.last_deployment.manual_actions.length > 0;
- },
-
- /**
- * Returns the value of the `stop_action?` key provided in the response.
- *
- * @returns {Boolean}
- */
- hasStopAction() {
- return this.model && this.model['stop_action?'];
- },
-
- /**
- * Verifies if the `deployable` key is present in `last_deployment` key.
- * Used to verify whether we should or not render the rollback partial.
- *
- * @returns {Boolean|Undefined}
- */
- canRetry() {
- return this.model &&
- this.hasLastDeploymentKey &&
- this.model.last_deployment &&
- this.model.last_deployment.deployable;
- },
-
- /**
- * Verifies if the date to be shown is present.
- *
- * @returns {Boolean|Undefined}
- */
- canShowDate() {
- return this.model &&
- this.model.last_deployment &&
- this.model.last_deployment.deployable &&
- this.model.last_deployment.deployable !== undefined;
- },
-
- /**
- * Human readable date.
- *
- * @returns {String}
- */
- createdDate() {
- if (this.model &&
- this.model.last_deployment &&
- this.model.last_deployment.deployable &&
- this.model.last_deployment.deployable.created_at) {
- return timeagoInstance.format(this.model.last_deployment.deployable.created_at);
- }
- return '';
- },
-
- /**
- * Returns the manual actions with the name parsed.
- *
- * @returns {Array.<Object>|Undefined}
- */
- manualActions() {
- if (this.hasManualActions) {
- return this.model.last_deployment.manual_actions.map((action) => {
- const parsedAction = {
- name: humanize(action.name),
- play_path: action.play_path,
- playable: action.playable,
- };
- return parsedAction;
- });
- }
- return [];
- },
-
- /**
- * Builds the string used in the user image alt attribute.
- *
- * @returns {String}
- */
- userImageAltDescription() {
- if (this.model &&
- this.model.last_deployment &&
- this.model.last_deployment.user &&
- this.model.last_deployment.user.username) {
- return `${this.model.last_deployment.user.username}'s avatar'`;
- }
- return '';
- },
-
- /**
- * If provided, returns the commit tag.
- *
- * @returns {String|Undefined}
- */
- commitTag() {
- if (this.model &&
- this.model.last_deployment &&
- this.model.last_deployment.tag) {
- return this.model.last_deployment.tag;
- }
- return undefined;
+ computed: {
+ /**
+ * Verifies if `last_deployment` key exists in the current Envrionment.
+ * This key is required to render most of the html - this method works has
+ * an helper.
+ *
+ * @returns {Boolean}
+ */
+ hasLastDeploymentKey() {
+ if (this.model &&
+ this.model.last_deployment &&
+ !_.isEmpty(this.model.last_deployment)) {
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Verifies is the given environment has manual actions.
+ * Used to verify if we should render them or nor.
+ *
+ * @returns {Boolean|Undefined}
+ */
+ hasManualActions() {
+ return this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.manual_actions &&
+ this.model.last_deployment.manual_actions.length > 0;
+ },
+
+ /**
+ * Returns the value of the `stop_action?` key provided in the response.
+ *
+ * @returns {Boolean}
+ */
+ hasStopAction() {
+ return this.model && this.model['stop_action?'];
+ },
+
+ /**
+ * Verifies if the `deployable` key is present in `last_deployment` key.
+ * Used to verify whether we should or not render the rollback partial.
+ *
+ * @returns {Boolean|Undefined}
+ */
+ canRetry() {
+ return this.model &&
+ this.hasLastDeploymentKey &&
+ this.model.last_deployment &&
+ this.model.last_deployment.deployable;
+ },
+
+ /**
+ * Verifies if the date to be shown is present.
+ *
+ * @returns {Boolean|Undefined}
+ */
+ canShowDate() {
+ return this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.deployable &&
+ this.model.last_deployment.deployable !== undefined;
+ },
+
+ /**
+ * Human readable date.
+ *
+ * @returns {String}
+ */
+ createdDate() {
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.deployable &&
+ this.model.last_deployment.deployable.created_at) {
+ return timeagoInstance.format(this.model.last_deployment.deployable.created_at);
+ }
+ return '';
+ },
+
+ /**
+ * Returns the manual actions with the name parsed.
+ *
+ * @returns {Array.<Object>|Undefined}
+ */
+ manualActions() {
+ if (this.hasManualActions) {
+ return this.model.last_deployment.manual_actions.map((action) => {
+ const parsedAction = {
+ name: humanize(action.name),
+ play_path: action.play_path,
+ playable: action.playable,
+ };
+ return parsedAction;
+ });
+ }
+ return [];
+ },
+
+ /**
+ * Builds the string used in the user image alt attribute.
+ *
+ * @returns {String}
+ */
+ userImageAltDescription() {
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.user &&
+ this.model.last_deployment.user.username) {
+ return `${this.model.last_deployment.user.username}'s avatar'`;
+ }
+ return '';
+ },
+
+ /**
+ * If provided, returns the commit tag.
+ *
+ * @returns {String|Undefined}
+ */
+ commitTag() {
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.tag) {
+ return this.model.last_deployment.tag;
+ }
+ return undefined;
+ },
+
+ /**
+ * If provided, returns the commit ref.
+ *
+ * @returns {Object|Undefined}
+ */
+ commitRef() {
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.ref) {
+ return this.model.last_deployment.ref;
+ }
+ return undefined;
+ },
+
+ /**
+ * If provided, returns the commit url.
+ *
+ * @returns {String|Undefined}
+ */
+ commitUrl() {
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.commit &&
+ this.model.last_deployment.commit.commit_path) {
+ return this.model.last_deployment.commit.commit_path;
+ }
+ return undefined;
+ },
+
+ /**
+ * If provided, returns the commit short sha.
+ *
+ * @returns {String|Undefined}
+ */
+ commitShortSha() {
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.commit &&
+ this.model.last_deployment.commit.short_id) {
+ return this.model.last_deployment.commit.short_id;
+ }
+ return undefined;
+ },
+
+ /**
+ * If provided, returns the commit title.
+ *
+ * @returns {String|Undefined}
+ */
+ commitTitle() {
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.commit &&
+ this.model.last_deployment.commit.title) {
+ return this.model.last_deployment.commit.title;
+ }
+ return undefined;
+ },
+
+ /**
+ * If provided, returns the commit tag.
+ *
+ * @returns {Object|Undefined}
+ */
+ commitAuthor() {
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.commit &&
+ this.model.last_deployment.commit.author) {
+ return this.model.last_deployment.commit.author;
+ }
+
+ return undefined;
+ },
+
+ /**
+ * Verifies if the `retry_path` key is present and returns its value.
+ *
+ * @returns {String|Undefined}
+ */
+ retryUrl() {
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.deployable &&
+ this.model.last_deployment.deployable.retry_path) {
+ return this.model.last_deployment.deployable.retry_path;
+ }
+ return undefined;
+ },
+
+ /**
+ * Verifies if the `last?` key is present and returns its value.
+ *
+ * @returns {Boolean|Undefined}
+ */
+ isLastDeployment() {
+ return this.model && this.model.last_deployment &&
+ this.model.last_deployment['last?'];
+ },
+
+ /**
+ * Builds the name of the builds needed to display both the name and the id.
+ *
+ * @returns {String}
+ */
+ buildName() {
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.deployable) {
+ const deployable = this.model.last_deployment.deployable;
+ return `${deployable.name} #${deployable.id}`;
+ }
+ return '';
+ },
+
+ /**
+ * Builds the needed string to show the internal id.
+ *
+ * @returns {String}
+ */
+ deploymentInternalId() {
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.iid) {
+ return `#${this.model.last_deployment.iid}`;
+ }
+ return '';
+ },
+
+ /**
+ * Verifies if the user object is present under last_deployment object.
+ *
+ * @returns {Boolean}
+ */
+ deploymentHasUser() {
+ return this.model &&
+ !_.isEmpty(this.model.last_deployment) &&
+ !_.isEmpty(this.model.last_deployment.user);
+ },
+
+ /**
+ * Returns the user object nested with the last_deployment object.
+ * Used to render the template.
+ *
+ * @returns {Object}
+ */
+ deploymentUser() {
+ if (this.model &&
+ !_.isEmpty(this.model.last_deployment) &&
+ !_.isEmpty(this.model.last_deployment.user)) {
+ return this.model.last_deployment.user;
+ }
+ return {};
+ },
+
+ /**
+ * Verifies if the build name column should be rendered by verifing
+ * if all the information needed is present
+ * and if the environment is not a folder.
+ *
+ * @returns {Boolean}
+ */
+ shouldRenderBuildName() {
+ return !this.model.isFolder &&
+ !_.isEmpty(this.model.last_deployment) &&
+ !_.isEmpty(this.model.last_deployment.deployable);
+ },
+
+ /**
+ * Verifies the presence of all the keys needed to render the buil_path.
+ *
+ * @return {String}
+ */
+ buildPath() {
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.deployable &&
+ this.model.last_deployment.deployable.build_path) {
+ return this.model.last_deployment.deployable.build_path;
+ }
+
+ return '';
+ },
+
+ /**
+ * Verifies the presence of all the keys needed to render the external_url.
+ *
+ * @return {String}
+ */
+ externalURL() {
+ if (this.model && this.model.external_url) {
+ return this.model.external_url;
+ }
+
+ return '';
+ },
+
+ /**
+ * Verifies if deplyment internal ID should be rendered by verifing
+ * if all the information needed is present
+ * and if the environment is not a folder.
+ *
+ * @returns {Boolean}
+ */
+ shouldRenderDeploymentID() {
+ return !this.model.isFolder &&
+ !_.isEmpty(this.model.last_deployment) &&
+ this.model.last_deployment.iid !== undefined;
+ },
+
+ environmentPath() {
+ if (this.model && this.model.environment_path) {
+ return this.model.environment_path;
+ }
+
+ return '';
+ },
+
+ monitoringUrl() {
+ if (this.model && this.model.metrics_path) {
+ return this.model.metrics_path;
+ }
+
+ return '';
+ },
+
+ displayEnvironmentActions() {
+ return this.hasManualActions ||
+ this.externalURL ||
+ this.monitoringUrl ||
+ this.hasStopAction ||
+ this.canRetry;
+ },
},
- /**
- * If provided, returns the commit ref.
- *
- * @returns {Object|Undefined}
- */
- commitRef() {
- if (this.model &&
- this.model.last_deployment &&
- this.model.last_deployment.ref) {
- return this.model.last_deployment.ref;
- }
- return undefined;
+ methods: {
+ onClickFolder() {
+ eventHub.$emit('toggleFolder', this.model);
+ },
},
-
- /**
- * If provided, returns the commit url.
- *
- * @returns {String|Undefined}
- */
- commitUrl() {
- if (this.model &&
- this.model.last_deployment &&
- this.model.last_deployment.commit &&
- this.model.last_deployment.commit.commit_path) {
- return this.model.last_deployment.commit.commit_path;
- }
- return undefined;
- },
-
- /**
- * If provided, returns the commit short sha.
- *
- * @returns {String|Undefined}
- */
- commitShortSha() {
- if (this.model &&
- this.model.last_deployment &&
- this.model.last_deployment.commit &&
- this.model.last_deployment.commit.short_id) {
- return this.model.last_deployment.commit.short_id;
- }
- return undefined;
- },
-
- /**
- * If provided, returns the commit title.
- *
- * @returns {String|Undefined}
- */
- commitTitle() {
- if (this.model &&
- this.model.last_deployment &&
- this.model.last_deployment.commit &&
- this.model.last_deployment.commit.title) {
- return this.model.last_deployment.commit.title;
- }
- return undefined;
- },
-
- /**
- * If provided, returns the commit tag.
- *
- * @returns {Object|Undefined}
- */
- commitAuthor() {
- if (this.model &&
- this.model.last_deployment &&
- this.model.last_deployment.commit &&
- this.model.last_deployment.commit.author) {
- return this.model.last_deployment.commit.author;
- }
-
- return undefined;
- },
-
- /**
- * Verifies if the `retry_path` key is present and returns its value.
- *
- * @returns {String|Undefined}
- */
- retryUrl() {
- if (this.model &&
- this.model.last_deployment &&
- this.model.last_deployment.deployable &&
- this.model.last_deployment.deployable.retry_path) {
- return this.model.last_deployment.deployable.retry_path;
- }
- return undefined;
- },
-
- /**
- * Verifies if the `last?` key is present and returns its value.
- *
- * @returns {Boolean|Undefined}
- */
- isLastDeployment() {
- return this.model && this.model.last_deployment &&
- this.model.last_deployment['last?'];
- },
-
- /**
- * Builds the name of the builds needed to display both the name and the id.
- *
- * @returns {String}
- */
- buildName() {
- if (this.model &&
- this.model.last_deployment &&
- this.model.last_deployment.deployable) {
- return `${this.model.last_deployment.deployable.name} #${this.model.last_deployment.deployable.id}`;
- }
- return '';
- },
-
- /**
- * Builds the needed string to show the internal id.
- *
- * @returns {String}
- */
- deploymentInternalId() {
- if (this.model &&
- this.model.last_deployment &&
- this.model.last_deployment.iid) {
- return `#${this.model.last_deployment.iid}`;
- }
- return '';
- },
-
- /**
- * Verifies if the user object is present under last_deployment object.
- *
- * @returns {Boolean}
- */
- deploymentHasUser() {
- return this.model &&
- !_.isEmpty(this.model.last_deployment) &&
- !_.isEmpty(this.model.last_deployment.user);
- },
-
- /**
- * Returns the user object nested with the last_deployment object.
- * Used to render the template.
- *
- * @returns {Object}
- */
- deploymentUser() {
- if (this.model &&
- !_.isEmpty(this.model.last_deployment) &&
- !_.isEmpty(this.model.last_deployment.user)) {
- return this.model.last_deployment.user;
- }
- return {};
- },
-
- /**
- * Verifies if the build name column should be rendered by verifing
- * if all the information needed is present
- * and if the environment is not a folder.
- *
- * @returns {Boolean}
- */
- shouldRenderBuildName() {
- return !this.model.isFolder &&
- !_.isEmpty(this.model.last_deployment) &&
- !_.isEmpty(this.model.last_deployment.deployable);
- },
-
- /**
- * Verifies the presence of all the keys needed to render the buil_path.
- *
- * @return {String}
- */
- buildPath() {
- if (this.model &&
- this.model.last_deployment &&
- this.model.last_deployment.deployable &&
- this.model.last_deployment.deployable.build_path) {
- return this.model.last_deployment.deployable.build_path;
- }
-
- return '';
- },
-
- /**
- * Verifies the presence of all the keys needed to render the external_url.
- *
- * @return {String}
- */
- externalURL() {
- if (this.model && this.model.external_url) {
- return this.model.external_url;
- }
-
- return '';
- },
-
- /**
- * Verifies if deplyment internal ID should be rendered by verifing
- * if all the information needed is present
- * and if the environment is not a folder.
- *
- * @returns {Boolean}
- */
- shouldRenderDeploymentID() {
- return !this.model.isFolder &&
- !_.isEmpty(this.model.last_deployment) &&
- this.model.last_deployment.iid !== undefined;
- },
-
- environmentPath() {
- if (this.model && this.model.environment_path) {
- return this.model.environment_path;
- }
-
- return '';
- },
-
- monitoringUrl() {
- if (this.model && this.model.metrics_path) {
- return this.model.metrics_path;
- }
-
- return '';
- },
-
- displayEnvironmentActions() {
- return this.hasManualActions ||
- this.externalURL ||
- this.monitoringUrl ||
- this.hasStopAction ||
- this.canRetry;
- },
- },
-
- methods: {
- onClickFolder() {
- eventHub.$emit('toggleFolder', this.model);
- },
- },
-};
+ };
</script>
<template>
<div
@@ -427,18 +428,22 @@ export default {
'folder-row': model.isFolder,
}"
role="row">
- <div class="table-section section-10" role="gridcell">
+ <div
+ class="table-section section-10"
+ role="gridcell"
+ >
<div
v-if="!model.isFolder"
class="table-mobile-header"
- role="rowheader">
- {{s__("Environments|Environment")}}
+ role="rowheader"
+ >
+ {{ s__("Environments|Environment") }}
</div>
<a
v-if="!model.isFolder"
class="environment-name flex-truncate-parent table-mobile-content"
:href="environmentPath">
- <span class="flex-truncate-child">{{model.name}}</span>
+ <span class="flex-truncate-child">{{ model.name }}</span>
</a>
<span
v-else
@@ -450,32 +455,40 @@ export default {
<i
v-show="model.isOpen"
class="fa fa-caret-down"
- aria-hidden="true" />
+ aria-hidden="true"
+ >
+ </i>
<i
v-show="!model.isOpen"
class="fa fa-caret-right"
- aria-hidden="true"/>
+ aria-hidden="true"
+ >
+ </i>
</span>
<span class="folder-icon">
<i
class="fa fa-folder"
- aria-hidden="true" />
+ aria-hidden="true">
+ </i>
</span>
<span>
- {{model.folderName}}
+ {{ model.folderName }}
</span>
<span class="badge">
- {{model.size}}
+ {{ model.size }}
</span>
</span>
</div>
- <div class="table-section section-10 deployment-column hidden-xs hidden-sm" role="gridcell">
+ <div
+ class="table-section section-10 deployment-column hidden-xs hidden-sm"
+ role="gridcell"
+ >
<span v-if="shouldRenderDeploymentID">
- {{deploymentInternalId}}
+ {{ deploymentInternalId }}
</span>
<span v-if="!model.isFolder && deploymentHasUser">
@@ -490,22 +503,29 @@ export default {
</span>
</div>
- <div class="table-section section-15 hidden-xs hidden-sm" role="gridcell">
+ <div
+ class="table-section section-15 hidden-xs hidden-sm"
+ role="gridcell"
+ >
<a
v-if="shouldRenderBuildName"
class="build-link flex-truncate-parent"
- :href="buildPath">
- <span class="flex-truncate-child">{{buildName}}</span>
+ :href="buildPath"
+ >
+ <span class="flex-truncate-child">{{ buildName }}</span>
</a>
</div>
<div
v-if="!model.isFolder"
- class="table-section section-25" role="gridcell">
+ class="table-section section-25"
+ role="gridcell"
+ >
<div
role="rowheader"
- class="table-mobile-header">
- {{s__("Environments|Commit")}}
+ class="table-mobile-header"
+ >
+ {{ s__("Environments|Commit") }}
</div>
<div
v-if="hasLastDeploymentKey"
@@ -521,22 +541,24 @@ export default {
<div
v-if="!hasLastDeploymentKey"
class="commit-title table-mobile-content">
- {{s__("Environments|No deployments yet")}}
+ {{ s__("Environments|No deployments yet") }}
</div>
</div>
<div
v-if="!model.isFolder"
- class="table-section section-10" role="gridcell">
+ class="table-section section-10"
+ role="gridcell"
+ >
<div
role="rowheader"
class="table-mobile-header">
- {{s__("Environments|Updated")}}
+ {{ s__("Environments|Updated") }}
</div>
<span
v-if="canShowDate"
class="environment-created-date-timeago table-mobile-content">
- {{createdDate}}
+ {{ createdDate }}
</span>
</div>
@@ -552,33 +574,33 @@ export default {
<actions-component
v-if="hasManualActions && canCreateDeployment"
:actions="manualActions"
- />
+ />
<external-url-component
v-if="externalURL && canReadEnvironment"
:external-url="externalURL"
- />
+ />
<monitoring-button-component
v-if="monitoringUrl && canReadEnvironment"
:monitoring-url="monitoringUrl"
- />
+ />
<terminal-button-component
v-if="model && model.terminal_path"
:terminal-path="model.terminal_path"
- />
+ />
<stop-component
v-if="hasStopAction && canCreateDeployment"
:stop-url="model.stop_path"
- />
+ />
<rollback-component
v-if="canRetry && canCreateDeployment"
:is-last-deployment="isLastDeployment"
:retry-url="retryUrl"
- />
+ />
</div>
</div>
</div>
diff --git a/app/assets/javascripts/environments/components/environment_monitoring.vue b/app/assets/javascripts/environments/components/environment_monitoring.vue
index b45af1a5ebc..081537cf218 100644
--- a/app/assets/javascripts/environments/components/environment_monitoring.vue
+++ b/app/assets/javascripts/environments/components/environment_monitoring.vue
@@ -1,27 +1,27 @@
<script>
-/**
- * Renders the Monitoring (Metrics) link in environments table.
- */
-import tooltip from '../../vue_shared/directives/tooltip';
+ /**
+ * Renders the Monitoring (Metrics) link in environments table.
+ */
+ import tooltip from '../../vue_shared/directives/tooltip';
-export default {
- props: {
- monitoringUrl: {
- type: String,
- required: true,
+ export default {
+ directives: {
+ tooltip,
},
- },
- directives: {
- tooltip,
- },
+ props: {
+ monitoringUrl: {
+ type: String,
+ required: true,
+ },
+ },
- computed: {
- title() {
- return 'Monitoring';
+ computed: {
+ title() {
+ return 'Monitoring';
+ },
},
- },
-};
+ };
</script>
<template>
<a
@@ -31,10 +31,12 @@ export default {
rel="noopener noreferrer nofollow"
:href="monitoringUrl"
:title="title"
- :aria-label="title">
+ :aria-label="title"
+ >
<i
class="fa fa-area-chart"
aria-hidden="true"
- />
+ >
+ </i>
</a>
</template>
diff --git a/app/assets/javascripts/environments/components/environment_rollback.vue b/app/assets/javascripts/environments/components/environment_rollback.vue
index 92a596bfd33..605a88e997e 100644
--- a/app/assets/javascripts/environments/components/environment_rollback.vue
+++ b/app/assets/javascripts/environments/components/environment_rollback.vue
@@ -1,57 +1,58 @@
<script>
-/**
- * Renders Rollback or Re deploy button in environments table depending
- * of the provided property `isLastDeployment`.
- *
- * Makes a post request when the button is clicked.
- */
-import eventHub from '../event_hub';
-import loadingIcon from '../../vue_shared/components/loading_icon.vue';
-
-export default {
- props: {
- retryUrl: {
- type: String,
- default: '',
+ /**
+ * Renders Rollback or Re deploy button in environments table depending
+ * of the provided property `isLastDeployment`.
+ *
+ * Makes a post request when the button is clicked.
+ */
+ import eventHub from '../event_hub';
+ import loadingIcon from '../../vue_shared/components/loading_icon.vue';
+
+ export default {
+ components: {
+ loadingIcon,
},
- isLastDeployment: {
- type: Boolean,
- default: true,
- },
- },
+ props: {
+ retryUrl: {
+ type: String,
+ default: '',
+ },
- components: {
- loadingIcon,
- },
+ isLastDeployment: {
+ type: Boolean,
+ default: true,
+ },
+ },
- data() {
- return {
- isLoading: false,
- };
- },
+ data() {
+ return {
+ isLoading: false,
+ };
+ },
- methods: {
- onClick() {
- this.isLoading = true;
+ methods: {
+ onClick() {
+ this.isLoading = true;
- eventHub.$emit('postAction', this.retryUrl);
+ eventHub.$emit('postAction', this.retryUrl);
+ },
},
- },
-};
+ };
</script>
<template>
<button
type="button"
class="btn hidden-xs hidden-sm"
@click="onClick"
- :disabled="isLoading">
+ :disabled="isLoading"
+ >
<span v-if="isLastDeployment">
- {{s__("Environments|Re-deploy")}}
+ {{ s__("Environments|Re-deploy") }}
</span>
<span v-else>
- {{s__("Environments|Rollback")}}
+ {{ s__("Environments|Rollback") }}
</span>
<loading-icon v-if="isLoading" />
diff --git a/app/assets/javascripts/environments/components/environment_stop.vue b/app/assets/javascripts/environments/components/environment_stop.vue
index 85f11d2071b..1eef17bf1fe 100644
--- a/app/assets/javascripts/environments/components/environment_stop.vue
+++ b/app/assets/javascripts/environments/components/environment_stop.vue
@@ -1,53 +1,53 @@
<script>
-/**
- * Renders the stop "button" that allows stop an environment.
- * Used in environments table.
- */
-import eventHub from '../event_hub';
-import loadingIcon from '../../vue_shared/components/loading_icon.vue';
-import tooltip from '../../vue_shared/directives/tooltip';
+ /**
+ * Renders the stop "button" that allows stop an environment.
+ * Used in environments table.
+ */
+ import eventHub from '../event_hub';
+ import loadingIcon from '../../vue_shared/components/loading_icon.vue';
+ import tooltip from '../../vue_shared/directives/tooltip';
-export default {
- props: {
- stopUrl: {
- type: String,
- default: '',
+ export default {
+ components: {
+ loadingIcon,
},
- },
- directives: {
- tooltip,
- },
+ directives: {
+ tooltip,
+ },
- data() {
- return {
- isLoading: false,
- };
- },
+ props: {
+ stopUrl: {
+ type: String,
+ default: '',
+ },
+ },
- components: {
- loadingIcon,
- },
+ data() {
+ return {
+ isLoading: false,
+ };
+ },
- computed: {
- title() {
- return 'Stop';
+ computed: {
+ title() {
+ return 'Stop';
+ },
},
- },
- methods: {
- onClick() {
- // eslint-disable-next-line no-alert
- if (confirm('Are you sure you want to stop this environment?')) {
- this.isLoading = true;
+ methods: {
+ onClick() {
+ // eslint-disable-next-line no-alert
+ if (confirm('Are you sure you want to stop this environment?')) {
+ this.isLoading = true;
- $(this.$el).tooltip('destroy');
+ $(this.$el).tooltip('destroy');
- eventHub.$emit('postAction', this.stopUrl);
- }
+ eventHub.$emit('postAction', this.stopUrl);
+ }
+ },
},
- },
-};
+ };
</script>
<template>
<button
@@ -58,10 +58,13 @@ export default {
@click="onClick"
:disabled="isLoading"
:title="title"
- :aria-label="title">
+ :aria-label="title"
+ >
<i
class="fa fa-stop stop-env-icon"
- aria-hidden="true" />
+ aria-hidden="true"
+ >
+ </i>
<loading-icon v-if="isLoading" />
</button>
</template>
diff --git a/app/assets/javascripts/environments/components/environment_terminal_button.vue b/app/assets/javascripts/environments/components/environment_terminal_button.vue
index 2037bf618e3..407d5333c0e 100644
--- a/app/assets/javascripts/environments/components/environment_terminal_button.vue
+++ b/app/assets/javascripts/environments/components/environment_terminal_button.vue
@@ -1,36 +1,36 @@
<script>
-/**
- * Renders a terminal button to open a web terminal.
- * Used in environments table.
- */
-import terminalIconSvg from 'icons/_icon_terminal.svg';
-import tooltip from '../../vue_shared/directives/tooltip';
+ /**
+ * Renders a terminal button to open a web terminal.
+ * Used in environments table.
+ */
+ import terminalIconSvg from 'icons/_icon_terminal.svg';
+ import tooltip from '../../vue_shared/directives/tooltip';
-export default {
- props: {
- terminalPath: {
- type: String,
- required: false,
- default: '',
+ export default {
+ directives: {
+ tooltip,
},
- },
- directives: {
- tooltip,
- },
+ props: {
+ terminalPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
- data() {
- return {
- terminalIconSvg,
- };
- },
+ data() {
+ return {
+ terminalIconSvg,
+ };
+ },
- computed: {
- title() {
- return 'Terminal';
+ computed: {
+ title() {
+ return 'Terminal';
+ },
},
- },
-};
+ };
</script>
<template>
<a
@@ -40,6 +40,7 @@ export default {
:title="title"
:aria-label="title"
:href="terminalPath"
- v-html="terminalIconSvg">
+ v-html="terminalIconSvg"
+ >
</a>
</template>
diff --git a/app/assets/javascripts/environments/components/environments_app.vue b/app/assets/javascripts/environments/components/environments_app.vue
index 2592909734f..c0be72f7401 100644
--- a/app/assets/javascripts/environments/components/environments_app.vue
+++ b/app/assets/javascripts/environments/components/environments_app.vue
@@ -7,6 +7,15 @@
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
export default {
+ components: {
+ emptyState,
+ },
+
+ mixins: [
+ CIPaginationMixin,
+ environmentsMixin,
+ ],
+
props: {
endpoint: {
type: String,
@@ -37,14 +46,6 @@
required: true,
},
},
- components: {
- emptyState,
- },
-
- mixins: [
- CIPaginationMixin,
- environmentsMixin,
- ],
created() {
eventHub.$on('toggleFolder', this.toggleFolder);
@@ -95,15 +96,17 @@
:tabs="tabs"
@onChangeTab="onChangeTab"
scope="environments"
- />
+ />
<div
v-if="canCreateEnvironment && !isLoading"
- class="nav-controls">
+ class="nav-controls"
+ >
<a
:href="newEnvironmentPath"
- class="btn btn-create">
- {{s__("Environments|New environment")}}
+ class="btn btn-create"
+ >
+ {{ s__("Environments|New environment") }}
</a>
</div>
</div>
@@ -116,13 +119,13 @@
:can-read-environment="canReadEnvironment"
@onChangePage="onChangePage"
>
- <empty-state
+ <empty-state
slot="emptyState"
v-if="!isLoading && state.environments.length === 0"
:new-path="newEnvironmentPath"
:help-path="helpPagePath"
:can-create-environment="canCreateEnvironment"
- />
+ />
</container>
</div>
</template>
diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue
index c04da4b81b7..858acf293a1 100644
--- a/app/assets/javascripts/environments/components/environments_table.vue
+++ b/app/assets/javascripts/environments/components/environments_table.vue
@@ -30,63 +30,96 @@ export default {
default: false,
},
},
-
methods: {
folderUrl(model) {
return `${window.location.pathname}/folders/${model.folderName}`;
},
+ shouldRenderFolderContent(env) {
+ return env.isFolder &&
+ env.isOpen &&
+ env.children &&
+ env.children.length > 0;
+ },
},
};
</script>
<template>
- <div class="ci-table" role="grid">
- <div class="gl-responsive-table-row table-row-header" role="row">
- <div class="table-section section-10 environments-name" role="columnheader">
- {{s__("Environments|Environment")}}
+ <div
+ class="ci-table"
+ role="grid"
+ >
+ <div
+ class="gl-responsive-table-row table-row-header"
+ role="row"
+ >
+ <div
+ class="table-section section-10 environments-name"
+ role="columnheader"
+ >
+ {{ s__("Environments|Environment") }}
</div>
- <div class="table-section section-10 environments-deploy" role="columnheader">
- {{s__("Environments|Deployment")}}
+ <div
+ class="table-section section-10 environments-deploy"
+ role="columnheader"
+ >
+ {{ s__("Environments|Deployment") }}
</div>
- <div class="table-section section-15 environments-build" role="columnheader">
- {{s__("Environments|Job")}}
+ <div
+ class="table-section section-15 environments-build"
+ role="columnheader"
+ >
+ {{ s__("Environments|Job") }}
</div>
- <div class="table-section section-25 environments-commit" role="columnheader">
- {{s__("Environments|Commit")}}
+ <div
+ class="table-section section-25 environments-commit"
+ role="columnheader"
+ >
+ {{ s__("Environments|Commit") }}
</div>
- <div class="table-section section-10 environments-date" role="columnheader">
- {{s__("Environments|Updated")}}
+ <div
+ class="table-section section-10 environments-date"
+ role="columnheader"
+ >
+ {{ s__("Environments|Updated") }}
</div>
</div>
<template
- v-for="model in environments"
- v-bind:model="model">
+ v-for="(model, i) in environments"
+ :model="model">
<div
is="environment-item"
:model="model"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
- />
+ :key="i"
+ />
- <template v-if="model.isFolder && model.isOpen && model.children && model.children.length > 0">
- <div v-if="model.isLoadingFolderContent">
+ <template
+ v-if="shouldRenderFolderContent(model)"
+ >
+ <div
+ v-if="model.isLoadingFolderContent"
+ :key="i">
<loading-icon size="2" />
</div>
<template v-else>
<div
is="environment-item"
- v-for="children in model.children"
+ v-for="(children, index) in model.children"
:model="children"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
- />
+ :key="index"
+ />
- <div>
+ <div :key="i">
<div class="text-center prepend-top-10">
<a
:href="folderUrl(model)"
- class="btn btn-default">
- {{s__("Environments|Show all")}}
+ class="btn btn-default"
+ >
+ {{ s__("Environments|Show all") }}
</a>
</div>
</div>
diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.vue b/app/assets/javascripts/environments/folder/environments_folder_view.vue
index 27418bad01a..5ef5e347387 100644
--- a/app/assets/javascripts/environments/folder/environments_folder_view.vue
+++ b/app/assets/javascripts/environments/folder/environments_folder_view.vue
@@ -3,6 +3,10 @@
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
export default {
+ mixins: [
+ environmentsMixin,
+ CIPaginationMixin,
+ ],
props: {
endpoint: {
type: String,
@@ -25,10 +29,6 @@
required: true,
},
},
- mixins: [
- environmentsMixin,
- CIPaginationMixin,
- ],
methods: {
successCallback(resp) {
this.saveData(resp);
@@ -40,17 +40,18 @@
<div :class="cssContainerClass">
<div
class="top-area"
- v-if="!isLoading">
+ v-if="!isLoading"
+ >
<h4 class="js-folder-name environments-folder-name">
- {{s__("Environments|Environments")}} / <b>{{folderName}}</b>
+ {{ s__("Environments|Environments") }} / <b>{{ folderName }}</b>
</h4>
<tabs
:tabs="tabs"
@onChangeTab="onChangeTab"
scope="environments"
- />
+ />
</div>
<container
diff --git a/app/assets/javascripts/filtered_search/recent_searches_root.js b/app/assets/javascripts/filtered_search/recent_searches_root.js
index 27e49d4fb96..c99ed63c4af 100644
--- a/app/assets/javascripts/filtered_search/recent_searches_root.js
+++ b/app/assets/javascripts/filtered_search/recent_searches_root.js
@@ -32,6 +32,9 @@ class RecentSearchesRoot {
const state = this.store.state;
this.vm = new Vue({
el: this.wrapperElement,
+ components: {
+ 'recent-searches-dropdown-content': RecentSearchesDropdownContent,
+ },
data() { return state; },
template: `
<recent-searches-dropdown-content
@@ -40,9 +43,6 @@ class RecentSearchesRoot {
:allowed-keys="allowedKeys"
/>
`,
- components: {
- 'recent-searches-dropdown-content': RecentSearchesDropdownContent,
- },
});
}
diff --git a/app/assets/javascripts/groups/components/app.vue b/app/assets/javascripts/groups/components/app.vue
index 241e026b84c..e035ba462db 100644
--- a/app/assets/javascripts/groups/components/app.vue
+++ b/app/assets/javascripts/groups/components/app.vue
@@ -1,16 +1,20 @@
<script>
/* global Flash */
+import { s__ } from '~/locale';
+import loadingIcon from '~/vue_shared/components/loading_icon.vue';
+import modal from '~/vue_shared/components/modal.vue';
+import { getParameterByName } from '~/lib/utils/common_utils';
+import { mergeUrlParams } from '~/lib/utils/url_utility';
+
import eventHub from '../event_hub';
-import { getParameterByName } from '../../lib/utils/common_utils';
-import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import { COMMON_STR } from '../constants';
-import { mergeUrlParams } from '../../lib/utils/url_utility';
import groupsComponent from './groups.vue';
export default {
components: {
loadingIcon,
+ modal,
groupsComponent,
},
props: {
@@ -32,6 +36,10 @@ export default {
isLoading: true,
isSearchEmpty: false,
searchEmptyMessage: '',
+ showModal: false,
+ groupLeaveConfirmationMessage: '',
+ targetGroup: null,
+ targetParentGroup: null,
};
},
computed: {
@@ -42,6 +50,26 @@ export default {
return this.store.getPaginationInfo();
},
},
+ created() {
+ this.searchEmptyMessage = this.hideProjects ?
+ COMMON_STR.GROUP_SEARCH_EMPTY : COMMON_STR.GROUP_PROJECT_SEARCH_EMPTY;
+
+ eventHub.$on('fetchPage', this.fetchPage);
+ eventHub.$on('toggleChildren', this.toggleChildren);
+ eventHub.$on('showLeaveGroupModal', this.showLeaveGroupModal);
+ eventHub.$on('updatePagination', this.updatePagination);
+ eventHub.$on('updateGroups', this.updateGroups);
+ },
+ mounted() {
+ this.fetchAllGroups();
+ },
+ beforeDestroy() {
+ eventHub.$off('fetchPage', this.fetchPage);
+ eventHub.$off('toggleChildren', this.toggleChildren);
+ eventHub.$off('showLeaveGroupModal', this.showLeaveGroupModal);
+ eventHub.$off('updatePagination', this.updatePagination);
+ eventHub.$off('updateGroups', this.updateGroups);
+ },
methods: {
fetchGroups({ parentId, page, filterGroupsBy, sortBy, archived, updatePagination }) {
return this.service.getGroups(parentId, page, filterGroupsBy, sortBy, archived)
@@ -121,14 +149,23 @@ export default {
parentGroup.isOpen = false;
}
},
- leaveGroup(group, parentGroup) {
- const targetGroup = group;
- targetGroup.isBeingRemoved = true;
- this.service.leaveGroup(targetGroup.leavePath)
+ showLeaveGroupModal(group, parentGroup) {
+ this.targetGroup = group;
+ this.targetParentGroup = parentGroup;
+ this.showModal = true;
+ this.groupLeaveConfirmationMessage = s__(`GroupsTree|Are you sure you want to leave the "${group.fullName}" group?`);
+ },
+ hideLeaveGroupModal() {
+ this.showModal = false;
+ },
+ leaveGroup() {
+ this.showModal = false;
+ this.targetGroup.isBeingRemoved = true;
+ this.service.leaveGroup(this.targetGroup.leavePath)
.then(res => res.json())
.then((res) => {
$.scrollTo(0);
- this.store.removeGroup(targetGroup, parentGroup);
+ this.store.removeGroup(this.targetGroup, this.targetParentGroup);
Flash(res.notice, 'notice');
})
.catch((err) => {
@@ -137,7 +174,7 @@ export default {
message = COMMON_STR.LEAVE_FORBIDDEN;
}
Flash(message);
- targetGroup.isBeingRemoved = false;
+ this.targetGroup.isBeingRemoved = false;
});
},
updatePagination(headers) {
@@ -152,26 +189,6 @@ export default {
}
},
},
- created() {
- this.searchEmptyMessage = this.hideProjects ?
- COMMON_STR.GROUP_SEARCH_EMPTY : COMMON_STR.GROUP_PROJECT_SEARCH_EMPTY;
-
- eventHub.$on('fetchPage', this.fetchPage);
- eventHub.$on('toggleChildren', this.toggleChildren);
- eventHub.$on('leaveGroup', this.leaveGroup);
- eventHub.$on('updatePagination', this.updatePagination);
- eventHub.$on('updateGroups', this.updateGroups);
- },
- mounted() {
- this.fetchAllGroups();
- },
- beforeDestroy() {
- eventHub.$off('fetchPage', this.fetchPage);
- eventHub.$off('toggleChildren', this.toggleChildren);
- eventHub.$off('leaveGroup', this.leaveGroup);
- eventHub.$off('updatePagination', this.updatePagination);
- eventHub.$off('updateGroups', this.updateGroups);
- },
};
</script>
@@ -190,5 +207,14 @@ export default {
:search-empty-message="searchEmptyMessage"
:page-info="pageInfo"
/>
+ <modal
+ v-show="showModal"
+ :primary-button-label="__('Leave')"
+ kind="warning"
+ :title="__('Are you sure?')"
+ :text="groupLeaveConfirmationMessage"
+ @cancel="hideLeaveGroupModal"
+ @submit="leaveGroup"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/groups/components/group_folder.vue b/app/assets/javascripts/groups/components/group_folder.vue
index e60221fa08d..647c9d0046d 100644
--- a/app/assets/javascripts/groups/components/group_folder.vue
+++ b/app/assets/javascripts/groups/components/group_folder.vue
@@ -20,7 +20,11 @@ export default {
return this.parentGroup.childrenCount > MAX_CHILDREN_COUNT;
},
moreChildrenStats() {
- return n__('One more item', '%d more items', this.parentGroup.childrenCount - this.parentGroup.children.length);
+ return n__(
+ 'One more item',
+ '%d more items',
+ this.parentGroup.childrenCount - this.parentGroup.children.length,
+ );
},
},
};
@@ -43,8 +47,9 @@ export default {
<i
class="fa fa-external-link"
aria-hidden="true"
- />
- {{moreChildrenStats}}
+ >
+ </i>
+ {{ moreChildrenStats }}
</a>
</li>
</ul>
diff --git a/app/assets/javascripts/groups/components/group_item.vue b/app/assets/javascripts/groups/components/group_item.vue
index 42e79a9e17a..764b130fdb8 100644
--- a/app/assets/javascripts/groups/components/group_item.vue
+++ b/app/assets/javascripts/groups/components/group_item.vue
@@ -75,7 +75,7 @@ export default {
:id="groupDomId"
:class="rowClass"
class="group-row"
- >
+ >
<div
class="group-row-contents"
:class="{ 'project-row-contents': !isGroup }">
@@ -88,7 +88,8 @@ export default {
:item="group"
/>
<div
- class="folder-toggle-wrap">
+ class="folder-toggle-wrap"
+ >
<item-caret
:is-group-open="group.isOpen"
/>
@@ -113,13 +114,14 @@ export default {
<identicon
v-else
size-class="s24"
- :entity-id=group.id
+ :entity-id="group.id"
:entity-name="group.name"
/>
</a>
</div>
<div
- class="title namespace-title">
+ class="title namespace-title"
+ >
<a
v-tooltip
:href="group.relativePath"
@@ -135,7 +137,7 @@ export default {
v-if="group.permission"
class="user-access-role"
>
- {{group.permission}}
+ {{ group.permission }}
</span>
</div>
<div
diff --git a/app/assets/javascripts/groups/components/groups.vue b/app/assets/javascripts/groups/components/groups.vue
index 75a2bf34887..adde8c8cdb3 100644
--- a/app/assets/javascripts/groups/components/groups.vue
+++ b/app/assets/javascripts/groups/components/groups.vue
@@ -1,47 +1,48 @@
<script>
-import tablePagination from '~/vue_shared/components/table_pagination.vue';
-import eventHub from '../event_hub';
-import { getParameterByName } from '../../lib/utils/common_utils';
+ import tablePagination from '~/vue_shared/components/table_pagination.vue';
+ import eventHub from '../event_hub';
+ import { getParameterByName } from '../../lib/utils/common_utils';
-export default {
- components: {
- tablePagination,
- },
- props: {
- groups: {
- type: Array,
- required: true,
+ export default {
+ components: {
+ tablePagination,
},
- pageInfo: {
- type: Object,
- required: true,
+ props: {
+ groups: {
+ type: Array,
+ required: true,
+ },
+ pageInfo: {
+ type: Object,
+ required: true,
+ },
+ searchEmpty: {
+ type: Boolean,
+ required: true,
+ },
+ searchEmptyMessage: {
+ type: String,
+ required: true,
+ },
},
- searchEmpty: {
- type: Boolean,
- required: true,
+ methods: {
+ change(page) {
+ const filterGroupsParam = getParameterByName('filter_groups');
+ const sortParam = getParameterByName('sort');
+ const archivedParam = getParameterByName('archived');
+ eventHub.$emit('fetchPage', page, filterGroupsParam, sortParam, archivedParam);
+ },
},
- searchEmptyMessage: {
- type: String,
- required: true,
- },
- },
- methods: {
- change(page) {
- const filterGroupsParam = getParameterByName('filter_groups');
- const sortParam = getParameterByName('sort');
- const archivedParam = getParameterByName('archived');
- eventHub.$emit('fetchPage', page, filterGroupsParam, sortParam, archivedParam);
- },
- },
-};
+ };
</script>
<template>
<div class="groups-list-tree-container">
<div
v-if="searchEmpty"
- class="has-no-search-results">
- {{searchEmptyMessage}}
+ class="has-no-search-results"
+ >
+ {{ searchEmptyMessage }}
</div>
<group-folder
v-if="!searchEmpty"
@@ -50,7 +51,7 @@ export default {
<table-pagination
v-if="!searchEmpty"
:change="change"
- :pageInfo="pageInfo"
+ :page-info="pageInfo"
/>
</div>
</template>
diff --git a/app/assets/javascripts/groups/components/item_actions.vue b/app/assets/javascripts/groups/components/item_actions.vue
index 0dd0783ce06..87065b3d6e3 100644
--- a/app/assets/javascripts/groups/components/item_actions.vue
+++ b/app/assets/javascripts/groups/components/item_actions.vue
@@ -1,15 +1,12 @@
<script>
-import { s__ } from '~/locale';
import tooltip from '~/vue_shared/directives/tooltip';
import icon from '~/vue_shared/components/icon.vue';
-import modal from '~/vue_shared/components/modal.vue';
import eventHub from '../event_hub';
import { COMMON_STR } from '../constants';
export default {
components: {
icon,
- modal,
},
directives: {
tooltip,
@@ -25,11 +22,6 @@ export default {
required: true,
},
},
- data() {
- return {
- modalStatus: false,
- };
- },
computed: {
leaveBtnTitle() {
return COMMON_STR.LEAVE_BTN_TITLE;
@@ -37,17 +29,10 @@ export default {
editBtnTitle() {
return COMMON_STR.EDIT_BTN_TITLE;
},
- leaveConfirmationMessage() {
- return s__(`GroupsTree|Are you sure you want to leave the "${this.group.fullName}" group?`);
- },
},
methods: {
onLeaveGroup() {
- this.modalStatus = true;
- },
- leaveGroup() {
- this.modalStatus = false;
- eventHub.$emit('leaveGroup', this.group, this.parentGroup);
+ eventHub.$emit('showLeaveGroupModal', this.group, this.parentGroup);
},
},
};
@@ -78,14 +63,5 @@ export default {
class="leave-group btn no-expand">
<icon name="leave"/>
</a>
- <modal
- v-show="modalStatus"
- :primary-button-label="__('Leave')"
- kind="warning"
- :title="__('Are you sure?')"
- :text="__('Are you sure you want to leave this group?')"
- :body="leaveConfirmationMessage"
- @submit="leaveGroup"
- />
</div>
</template>
diff --git a/app/assets/javascripts/groups/components/item_caret.vue b/app/assets/javascripts/groups/components/item_caret.vue
index 9e90fe2b701..2a5bec5e86c 100644
--- a/app/assets/javascripts/groups/components/item_caret.vue
+++ b/app/assets/javascripts/groups/components/item_caret.vue
@@ -2,6 +2,9 @@
import icon from '~/vue_shared/components/icon.vue';
export default {
+ components: {
+ icon,
+ },
props: {
isGroupOpen: {
type: Boolean,
@@ -9,9 +12,6 @@ export default {
default: false,
},
},
- components: {
- icon,
- },
computed: {
iconClass() {
return this.isGroupOpen ? 'angle-down' : 'angle-right';
diff --git a/app/assets/javascripts/groups/components/item_stats.vue b/app/assets/javascripts/groups/components/item_stats.vue
index 2e42fb6c9a6..168b4e4af2c 100644
--- a/app/assets/javascripts/groups/components/item_stats.vue
+++ b/app/assets/javascripts/groups/components/item_stats.vue
@@ -1,39 +1,44 @@
<script>
-import icon from '~/vue_shared/components/icon.vue';
-import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
-import { ITEM_TYPE, VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE, PROJECT_VISIBILITY_TYPE } from '../constants';
-import itemStatsValue from './item_stats_value.vue';
+ import icon from '~/vue_shared/components/icon.vue';
+ import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+ import {
+ ITEM_TYPE,
+ VISIBILITY_TYPE_ICON,
+ GROUP_VISIBILITY_TYPE,
+ PROJECT_VISIBILITY_TYPE,
+ } from '../constants';
+ import itemStatsValue from './item_stats_value.vue';
-export default {
- components: {
- icon,
- timeAgoTooltip,
- itemStatsValue,
- },
- props: {
- item: {
- type: Object,
- required: true,
+ export default {
+ components: {
+ icon,
+ timeAgoTooltip,
+ itemStatsValue,
},
- },
- computed: {
- visibilityIcon() {
- return VISIBILITY_TYPE_ICON[this.item.visibility];
+ props: {
+ item: {
+ type: Object,
+ required: true,
+ },
},
- visibilityTooltip() {
- if (this.item.type === ITEM_TYPE.GROUP) {
- return GROUP_VISIBILITY_TYPE[this.item.visibility];
- }
- return PROJECT_VISIBILITY_TYPE[this.item.visibility];
+ computed: {
+ visibilityIcon() {
+ return VISIBILITY_TYPE_ICON[this.item.visibility];
+ },
+ visibilityTooltip() {
+ if (this.item.type === ITEM_TYPE.GROUP) {
+ return GROUP_VISIBILITY_TYPE[this.item.visibility];
+ }
+ return PROJECT_VISIBILITY_TYPE[this.item.visibility];
+ },
+ isProject() {
+ return this.item.type === ITEM_TYPE.PROJECT;
+ },
+ isGroup() {
+ return this.item.type === ITEM_TYPE.GROUP;
+ },
},
- isProject() {
- return this.item.type === ITEM_TYPE.PROJECT;
- },
- isGroup() {
- return this.item.type === ITEM_TYPE.GROUP;
- },
- },
-};
+ };
</script>
<template>
diff --git a/app/assets/javascripts/groups/components/item_stats_value.vue b/app/assets/javascripts/groups/components/item_stats_value.vue
index f441cabf6d2..08d0bf6e344 100644
--- a/app/assets/javascripts/groups/components/item_stats_value.vue
+++ b/app/assets/javascripts/groups/components/item_stats_value.vue
@@ -1,52 +1,52 @@
<script>
-import tooltip from '~/vue_shared/directives/tooltip';
-import icon from '~/vue_shared/components/icon.vue';
+ import tooltip from '~/vue_shared/directives/tooltip';
+ import icon from '~/vue_shared/components/icon.vue';
-export default {
- props: {
- title: {
- type: String,
- required: false,
- default: '',
+ export default {
+ components: {
+ icon,
},
- cssClass: {
- type: String,
- required: false,
- default: '',
+ directives: {
+ tooltip,
},
- iconName: {
- type: String,
- required: true,
+ props: {
+ title: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ cssClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ iconName: {
+ type: String,
+ required: true,
+ },
+ tooltipPlacement: {
+ type: String,
+ required: false,
+ default: 'bottom',
+ },
+ /**
+ * value could either be number or string
+ * as `memberCount` is always passed as string
+ * while `subgroupCount` & `projectCount`
+ * are always number
+ */
+ value: {
+ type: [Number, String],
+ required: false,
+ default: '',
+ },
},
- tooltipPlacement: {
- type: String,
- required: false,
- default: 'bottom',
+ computed: {
+ isValuePresent() {
+ return this.value !== '';
+ },
},
- /**
- * value could either be number or string
- * as `memberCount` is always passed as string
- * while `subgroupCount` & `projectCount`
- * are always number
- */
- value: {
- type: [Number, String],
- required: false,
- default: '',
- },
- },
- directives: {
- tooltip,
- },
- components: {
- icon,
- },
- computed: {
- isValuePresent() {
- return this.value !== '';
- },
- },
-};
+ };
</script>
<template>
@@ -57,12 +57,12 @@ export default {
:class="cssClass"
:title="title"
>
- <icon :name="iconName"/>
+ <icon :name="iconName" />
<span
v-if="isValuePresent"
class="stat-value"
>
- {{value}}
+ {{ value }}
</span>
</span>
</template>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list.vue b/app/assets/javascripts/ide/components/commit_sidebar/list.vue
index 704dff981df..a8459b011df 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/list.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/list.vue
@@ -32,7 +32,6 @@
this.$emit('toggleCollapsed');
},
},
-
};
</script>
diff --git a/app/assets/javascripts/ide/components/ide.vue b/app/assets/javascripts/ide/components/ide.vue
index 26a70f6e748..89981ab2c65 100644
--- a/app/assets/javascripts/ide/components/ide.vue
+++ b/app/assets/javascripts/ide/components/ide.vue
@@ -1,79 +1,82 @@
<script>
-import { mapState, mapGetters } from 'vuex';
-import ideSidebar from './ide_side_bar.vue';
-import ideContextbar from './ide_context_bar.vue';
-import repoTabs from './repo_tabs.vue';
-import repoFileButtons from './repo_file_buttons.vue';
-import ideStatusBar from './ide_status_bar.vue';
-import repoPreview from './repo_preview.vue';
-import repoEditor from './repo_editor.vue';
+ import { mapState, mapGetters } from 'vuex';
+ import ideSidebar from './ide_side_bar.vue';
+ import ideContextbar from './ide_context_bar.vue';
+ import repoTabs from './repo_tabs.vue';
+ import repoFileButtons from './repo_file_buttons.vue';
+ import ideStatusBar from './ide_status_bar.vue';
+ import repoPreview from './repo_preview.vue';
+ import repoEditor from './repo_editor.vue';
-export default {
- props: {
- emptyStateSvgPath: {
- type: String,
- required: true,
+ export default {
+ components: {
+ ideSidebar,
+ ideContextbar,
+ repoTabs,
+ repoFileButtons,
+ ideStatusBar,
+ repoEditor,
+ repoPreview,
},
- },
- computed: {
- ...mapState([
- 'currentBlobView',
- 'selectedFile',
- ]),
- ...mapGetters([
- 'changedFiles',
- 'activeFile',
- ]),
- },
- components: {
- ideSidebar,
- ideContextbar,
- repoTabs,
- repoFileButtons,
- ideStatusBar,
- repoEditor,
- repoPreview,
- },
- mounted() {
- const returnValue = 'Are you sure you want to lose unsaved changes?';
- window.onbeforeunload = (e) => {
- if (!this.changedFiles.length) return undefined;
+ props: {
+ emptyStateSvgPath: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ ...mapState([
+ 'currentBlobView',
+ 'selectedFile',
+ ]),
+ ...mapGetters([
+ 'changedFiles',
+ 'activeFile',
+ ]),
+ },
+ mounted() {
+ const returnValue = 'Are you sure you want to lose unsaved changes?';
+ window.onbeforeunload = (e) => {
+ if (!this.changedFiles.length) return undefined;
- Object.assign(e, {
- returnValue,
- });
- return returnValue;
- };
- },
-};
+ Object.assign(e, {
+ returnValue,
+ });
+ return returnValue;
+ };
+ },
+ };
</script>
<template>
- <div
+ <div
class="ide-view"
>
- <ide-sidebar/>
+ <ide-sidebar />
<div
class="multi-file-edit-pane"
>
<template
- v-if="activeFile">
+ v-if="activeFile"
+ >
<repo-tabs/>
<component
class="multi-file-edit-pane-content"
:is="currentBlobView"
/>
- <repo-file-buttons/>
+ <repo-file-buttons />
<ide-status-bar
- :file="selectedFile"/>
+ :file="selectedFile"
+ />
</template>
<template
- v-else>
+ v-else
+ >
<div class="ide-empty-state">
<div class="row js-empty-state">
<div class="col-xs-12">
<div class="svg-content svg-250">
- <img :src="emptyStateSvgPath">
+ <img :src="emptyStateSvgPath" />
</div>
</div>
<div class="col-xs-12">
@@ -82,7 +85,8 @@ export default {
Welcome to the GitLab IDE
</h4>
<p>
- You can select a file in the left sidebar to begin editing and use the right sidebar to commit your changes.
+ You can select a file in the left sidebar to begin
+ editing and use the right sidebar to commit your changes.
</p>
</div>
</div>
diff --git a/app/assets/javascripts/ide/components/ide_context_bar.vue b/app/assets/javascripts/ide/components/ide_context_bar.vue
index 78c01272af6..dd947f66969 100644
--- a/app/assets/javascripts/ide/components/ide_context_bar.vue
+++ b/app/assets/javascripts/ide/components/ide_context_bar.vue
@@ -1,59 +1,59 @@
<script>
-import { mapGetters, mapState, mapActions } from 'vuex';
-import repoCommitSection from './repo_commit_section.vue';
-import icon from '../../vue_shared/components/icon.vue';
-import panelResizer from '../../vue_shared/components/panel_resizer.vue';
+ import { mapGetters, mapState, mapActions } from 'vuex';
+ import repoCommitSection from './repo_commit_section.vue';
+ import icon from '../../vue_shared/components/icon.vue';
+ import panelResizer from '../../vue_shared/components/panel_resizer.vue';
-export default {
- data() {
- return {
- width: 290,
- };
- },
- components: {
- repoCommitSection,
- icon,
- panelResizer,
- },
- computed: {
- ...mapState([
- 'rightPanelCollapsed',
- ]),
- ...mapGetters([
- 'changedFiles',
- ]),
- currentIcon() {
- return this.rightPanelCollapsed ? 'angle-double-left' : 'angle-double-right';
+ export default {
+ components: {
+ repoCommitSection,
+ icon,
+ panelResizer,
},
- maxSize() {
- return window.innerWidth / 2;
+ data() {
+ return {
+ width: 290,
+ };
},
- panelStyle() {
- if (!this.rightPanelCollapsed) {
- return { width: `${this.width}px` };
- }
- return {};
+ computed: {
+ ...mapState([
+ 'rightPanelCollapsed',
+ ]),
+ ...mapGetters([
+ 'changedFiles',
+ ]),
+ currentIcon() {
+ return this.rightPanelCollapsed ? 'angle-double-left' : 'angle-double-right';
+ },
+ maxSize() {
+ return window.innerWidth / 2;
+ },
+ panelStyle() {
+ if (!this.rightPanelCollapsed) {
+ return { width: `${this.width}px` };
+ }
+ return {};
+ },
},
- },
- methods: {
- ...mapActions([
- 'setPanelCollapsedStatus',
- 'setResizingStatus',
- ]),
- toggleCollapsed() {
- this.setPanelCollapsedStatus({
- side: 'right',
- collapsed: !this.rightPanelCollapsed,
- });
+ methods: {
+ ...mapActions([
+ 'setPanelCollapsedStatus',
+ 'setResizingStatus',
+ ]),
+ toggleCollapsed() {
+ this.setPanelCollapsedStatus({
+ side: 'right',
+ collapsed: !this.rightPanelCollapsed,
+ });
+ },
+ resizingStarted() {
+ this.setResizingStatus(true);
+ },
+ resizingEnded() {
+ this.setResizingStatus(false);
+ },
},
- resizingStarted() {
- this.setResizingStatus(true);
- },
- resizingEnded() {
- this.setResizingStatus(false);
- },
- },
-};
+ };
</script>
<template>
@@ -64,17 +64,17 @@ export default {
}"
:style="panelStyle"
>
- <div
- class="multi-file-commit-panel-section">
+ <div class="multi-file-commit-panel-section">
<header
class="multi-file-commit-panel-header"
:class="{
- 'is-collapsed': rightPanelCollapsed,
- }"
- >
+ 'is-collapsed': rightPanelCollapsed,
+ }"
+ >
<div
class="multi-file-commit-panel-header-title"
- v-if="!rightPanelCollapsed">
+ v-if="!rightPanelCollapsed"
+ >
<icon
name="list-bulleted"
:size="18"
@@ -92,8 +92,7 @@ export default {
/>
</button>
</header>
- <repo-commit-section
- class=""/>
+ <repo-commit-section />
</div>
<panel-resizer
:size.sync="width"
@@ -103,6 +102,7 @@ export default {
:max-size="maxSize"
@resize-start="resizingStarted"
@resize-end="resizingEnded"
- side="left"/>
+ side="left"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/ide/components/ide_project_branches_tree.vue b/app/assets/javascripts/ide/components/ide_project_branches_tree.vue
index bd3a521ff43..af2f7341a91 100644
--- a/app/assets/javascripts/ide/components/ide_project_branches_tree.vue
+++ b/app/assets/javascripts/ide/components/ide_project_branches_tree.vue
@@ -28,20 +28,20 @@ export default {
<div class="branch-header-title">
<icon
name="branch"
- :size="12">
- </icon>
+ :size="12"
+ />
{{ branch.name }}
</div>
<div class="branch-header-btns">
<new-dropdown
:project-id="projectId"
:branch="branch.name"
- path=""/>
+ path=""
+ />
</div>
</div>
<div>
- <repo-tree
- :treeId="branch.treeId"/>
+ <repo-tree :tree-id="branch.treeId" />
</div>
</div>
</template>
diff --git a/app/assets/javascripts/ide/components/ide_project_tree.vue b/app/assets/javascripts/ide/components/ide_project_tree.vue
index 61daba6d176..ed49a0e72a2 100644
--- a/app/assets/javascripts/ide/components/ide_project_tree.vue
+++ b/app/assets/javascripts/ide/components/ide_project_tree.vue
@@ -19,9 +19,10 @@ export default {
<template>
<div class="projects-sidebar">
<div class="context-header">
- <a
- :title="project.name"
- :href="project.web_url">
+ <a
+ :title="project.name"
+ :href="project.web_url"
+ >
<div class="avatar-container s40 project-avatar">
<project-avatar-image
class="avatar-container project-avatar"
@@ -29,7 +30,7 @@ export default {
:img-src="project.avatar_url"
:img-alt="project.name"
:img-size="40"
- />
+ />
</div>
<div class="sidebar-context-title">
{{ project.name }}
@@ -38,10 +39,11 @@ export default {
</div>
<div class="multi-file-commit-panel-inner-scroll">
<branches-tree
- v-for="(branch, index) in project.branches"
+ v-for="branch in project.branches"
:key="branch.name"
:project-id="project.path_with_namespace"
- :branch="branch"/>
+ :branch="branch"
+ />
</div>
</div>
</template>
diff --git a/app/assets/javascripts/ide/components/ide_repo_tree.vue b/app/assets/javascripts/ide/components/ide_repo_tree.vue
index bd89ebe47d9..4651e345d75 100644
--- a/app/assets/javascripts/ide/components/ide_repo_tree.vue
+++ b/app/assets/javascripts/ide/components/ide_repo_tree.vue
@@ -44,28 +44,31 @@ export default {
</script>
<template>
-<div>
- <div class="ide-file-list">
- <table class="table">
- <tbody
- v-if="treeId">
- <repo-previous-directory
- v-if="hasPreviousDirectory"
- />
- <div
- class="multi-file-loading-container"
- v-if="showLoading"
- v-for="n in 3"
- :key="n">
- <skeleton-loading-container/>
- </div>
- <repo-file
- v-for="file in fetchedList"
- :key="file.key"
- :file="file"
- />
- </tbody>
- </table>
+ <div>
+ <div class="ide-file-list">
+ <table class="table">
+ <tbody
+ v-if="treeId"
+ >
+ <repo-previous-directory
+ v-if="hasPreviousDirectory"
+ />
+ <template v-if="showLoading">
+ <div
+ class="multi-file-loading-container"
+ v-for="n in 3"
+ :key="n"
+ >
+ <skeleton-loading-container />
+ </div>
+ </template>
+ <repo-file
+ v-for="file in fetchedList"
+ :key="file.key"
+ :file="file"
+ />
+ </tbody>
+ </table>
+ </div>
</div>
-</div>
</template>
diff --git a/app/assets/javascripts/ide/components/ide_side_bar.vue b/app/assets/javascripts/ide/components/ide_side_bar.vue
index c30018e04b0..a68f8ce0169 100644
--- a/app/assets/javascripts/ide/components/ide_side_bar.vue
+++ b/app/assets/javascripts/ide/components/ide_side_bar.vue
@@ -1,85 +1,88 @@
<script>
-import { mapState, mapActions } from 'vuex';
-import projectTree from './ide_project_tree.vue';
-import icon from '../../vue_shared/components/icon.vue';
-import panelResizer from '../../vue_shared/components/panel_resizer.vue';
-import skeletonLoadingContainer from '../../vue_shared/components/skeleton_loading_container.vue';
+ import { mapState, mapActions } from 'vuex';
+ import projectTree from './ide_project_tree.vue';
+ import icon from '../../vue_shared/components/icon.vue';
+ import panelResizer from '../../vue_shared/components/panel_resizer.vue';
+ import skeletonLoadingContainer from '../../vue_shared/components/skeleton_loading_container.vue';
-export default {
- data() {
- return {
- width: 290,
- };
- },
- components: {
- projectTree,
- icon,
- panelResizer,
- skeletonLoadingContainer,
- },
- computed: {
- ...mapState([
- 'loading',
- 'projects',
- 'leftPanelCollapsed',
- ]),
- currentIcon() {
- return this.leftPanelCollapsed ? 'angle-double-right' : 'angle-double-left';
+ export default {
+ components: {
+ projectTree,
+ icon,
+ panelResizer,
+ skeletonLoadingContainer,
},
- maxSize() {
- return window.innerWidth / 2;
+ data() {
+ return {
+ width: 290,
+ };
},
- panelStyle() {
- if (!this.leftPanelCollapsed) {
- return { width: `${this.width}px` };
- }
- return {};
+ computed: {
+ ...mapState([
+ 'loading',
+ 'projects',
+ 'leftPanelCollapsed',
+ ]),
+ currentIcon() {
+ return this.leftPanelCollapsed ? 'angle-double-right' : 'angle-double-left';
+ },
+ maxSize() {
+ return window.innerWidth / 2;
+ },
+ panelStyle() {
+ if (!this.leftPanelCollapsed) {
+ return { width: `${this.width}px` };
+ }
+ return {};
+ },
+ showLoading() {
+ return this.loading;
+ },
},
- showLoading() {
- return this.loading;
+ methods: {
+ ...mapActions([
+ 'setPanelCollapsedStatus',
+ 'setResizingStatus',
+ ]),
+ toggleCollapsed() {
+ this.setPanelCollapsedStatus({
+ side: 'left',
+ collapsed: !this.leftPanelCollapsed,
+ });
+ },
+ resizingStarted() {
+ this.setResizingStatus(true);
+ },
+ resizingEnded() {
+ this.setResizingStatus(false);
+ },
},
- },
- methods: {
- ...mapActions([
- 'setPanelCollapsedStatus',
- 'setResizingStatus',
- ]),
- toggleCollapsed() {
- this.setPanelCollapsedStatus({
- side: 'left',
- collapsed: !this.leftPanelCollapsed,
- });
- },
- resizingStarted() {
- this.setResizingStatus(true);
- },
- resizingEnded() {
- this.setResizingStatus(false);
- },
- },
-};
+ };
</script>
<template>
<div
- class="multi-file-commit-panel"
- :class="{
- 'is-collapsed': leftPanelCollapsed,
- }"
- :style="panelStyle"
- >
+ class="multi-file-commit-panel"
+ :class="{
+ 'is-collapsed': leftPanelCollapsed,
+ }"
+ :style="panelStyle"
+ >
<div class="multi-file-commit-panel-inner">
- <div
- class="multi-file-loading-container"
- v-if="showLoading"
- v-for="n in 3"
- :key="n">
- <skeleton-loading-container/>
- </div>
+ <template v-if="showLoading">
+ <div
+ class="multi-file-loading-container"
+ v-for="n in 3"
+ :key="n"
+ >
+ <skeleton-loading-container />
+ </div>
+ </template>
<project-tree
- v-for="(project, index) in projects"
+ v-for="project in projects"
:key="project.id"
- :project="project"/>
+ :project="project"
+ />
</div>
<button
type="button"
@@ -93,7 +96,9 @@ export default {
<span
v-if="!leftPanelCollapsed"
class="collapse-text"
- >Collapse sidebar</span>
+ >
+ Collapse sidebar
+ </span>
</button>
<panel-resizer
:size.sync="width"
@@ -103,6 +108,7 @@ export default {
:max-size="maxSize"
@resize-start="resizingStarted"
@resize-end="resizingEnded"
- side="right"/>
+ side="right"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/ide/components/ide_status_bar.vue b/app/assets/javascripts/ide/components/ide_status_bar.vue
index a24abadd936..e48c446c4a4 100644
--- a/app/assets/javascripts/ide/components/ide_status_bar.vue
+++ b/app/assets/javascripts/ide/components/ide_status_bar.vue
@@ -1,70 +1,65 @@
<script>
-import { mapState } from 'vuex';
-import icon from '../../vue_shared/components/icon.vue';
-import tooltip from '../../vue_shared/directives/tooltip';
-import timeAgoMixin from '../../vue_shared/mixins/timeago';
+ import { mapState } from 'vuex';
+ import icon from '../../vue_shared/components/icon.vue';
+ import tooltip from '../../vue_shared/directives/tooltip';
+ import timeAgoMixin from '../../vue_shared/mixins/timeago';
-export default {
- props: {
- file: {
- type: Object,
- required: true,
+ export default {
+ components: {
+ icon,
},
- },
- components: {
- icon,
- },
- directives: {
- tooltip,
- },
- mixins: [
- timeAgoMixin,
- ],
- computed: {
- ...mapState([
- 'selectedFile',
- ]),
- },
-};
+ directives: {
+ tooltip,
+ },
+ mixins: [
+ timeAgoMixin,
+ ],
+ props: {
+ file: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ ...mapState([
+ 'selectedFile',
+ ]),
+ },
+ };
</script>
<template>
- <div
- class="ide-status-bar">
+ <div class="ide-status-bar">
<div>
<icon
name="branch"
- :size="12">
- </icon>
+ :size="12"
+ />
{{ selectedFile.branchId }}
</div>
<div>
- <div
- v-if="selectedFile.lastCommit && selectedFile.lastCommit.id">
+ <div v-if="selectedFile.lastCommit && selectedFile.lastCommit.id">
Last commit:
<a
v-tooltip
:title="selectedFile.lastCommit.message"
- :href="selectedFile.lastCommit.url">
- {{ timeFormated(selectedFile.lastCommit.updatedAt) }} by
+ :href="selectedFile.lastCommit.url"
+ >
+ {{ timeFormated(selectedFile.lastCommit.updatedAt) }} by
{{ selectedFile.lastCommit.author }}
</a>
- </div>
+ </div>
</div>
- <div
- class="text-right">
+ <div class="text-right">
{{ selectedFile.name }}
</div>
- <div
- class="text-right">
+ <div class="text-right">
{{ selectedFile.eol }}
</div>
- <div
- class="text-right">
+ <div class="text-right">
{{ file.editorRow }}:{{ file.editorColumn }}
</div>
- <div
- class="text-right">
+ <div class="text-right">
{{ selectedFile.fileLanguage }}
</div>
</div>
diff --git a/app/assets/javascripts/ide/components/new_branch_form.vue b/app/assets/javascripts/ide/components/new_branch_form.vue
index 2119d373d31..56e31256132 100644
--- a/app/assets/javascripts/ide/components/new_branch_form.vue
+++ b/app/assets/javascripts/ide/components/new_branch_form.vue
@@ -21,6 +21,13 @@
return this.loading || this.branchName === '';
},
},
+ created() {
+ // Dropdown is outside of Vue instance & is controlled by Bootstrap
+ this.$dropdown = $('.git-revision-dropdown');
+
+ // text element is outside Vue app
+ this.dropdownText = document.querySelector('.project-refs-form .dropdown-toggle-text');
+ },
methods: {
...mapActions([
'createNewBranch',
@@ -55,13 +62,6 @@
}));
},
},
- created() {
- // Dropdown is outside of Vue instance & is controlled by Bootstrap
- this.$dropdown = $('.git-revision-dropdown');
-
- // text element is outside Vue app
- this.dropdownText = document.querySelector('.project-refs-form .dropdown-toggle-text');
- },
};
</script>
diff --git a/app/assets/javascripts/ide/components/new_dropdown/index.vue b/app/assets/javascripts/ide/components/new_dropdown/index.vue
index d475813c4f7..ef653357f5f 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/index.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/index.vue
@@ -4,6 +4,11 @@
import icon from '../../../vue_shared/components/icon.vue';
export default {
+ components: {
+ icon,
+ newModal,
+ upload,
+ },
props: {
branch: {
type: String,
@@ -18,11 +23,6 @@
default: null,
},
},
- components: {
- icon,
- newModal,
- upload,
- },
data() {
return {
openModal: false,
diff --git a/app/assets/javascripts/ide/components/new_dropdown/modal.vue b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
index 0312f56efbd..36cd825c6dd 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/modal.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
@@ -4,6 +4,9 @@
import modal from '../../../vue_shared/components/modal.vue';
export default {
+ components: {
+ modal,
+ },
props: {
branchId: {
type: String,
@@ -27,28 +30,6 @@
entryName: this.path !== '' ? `${this.path}/` : '',
};
},
- components: {
- modal,
- },
- methods: {
- ...mapActions([
- 'createTempEntry',
- ]),
- createEntryInStore() {
- this.createTempEntry({
- projectId: this.currentProjectId,
- branchId: this.branchId,
- parent: this.parent,
- name: this.entryName.replace(new RegExp(`^${this.path}/`), ''),
- type: this.type,
- });
-
- this.hideModal();
- },
- hideModal() {
- this.$emit('hide');
- },
- },
computed: {
...mapState([
'currentProjectId',
@@ -78,6 +59,25 @@
mounted() {
this.$refs.fieldName.focus();
},
+ methods: {
+ ...mapActions([
+ 'createTempEntry',
+ ]),
+ createEntryInStore() {
+ this.createTempEntry({
+ projectId: this.currentProjectId,
+ branchId: this.branchId,
+ parent: this.parent,
+ name: this.entryName.replace(new RegExp(`^${this.path}/`), ''),
+ type: this.type,
+ });
+
+ this.hideModal();
+ },
+ hideModal() {
+ this.$emit('hide');
+ },
+ },
};
</script>
diff --git a/app/assets/javascripts/ide/components/new_dropdown/upload.vue b/app/assets/javascripts/ide/components/new_dropdown/upload.vue
index 2a2f2a241fc..6244737fa43 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/upload.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/upload.vue
@@ -18,6 +18,12 @@
'currentProjectId',
]),
},
+ mounted() {
+ this.$refs.fileUpload.addEventListener('change', this.openFile);
+ },
+ beforeDestroy() {
+ this.$refs.fileUpload.removeEventListener('change', this.openFile);
+ },
methods: {
...mapActions([
'createTempEntry',
@@ -59,12 +65,6 @@
this.$refs.fileUpload.click();
},
},
- mounted() {
- this.$refs.fileUpload.addEventListener('change', this.openFile);
- },
- beforeDestroy() {
- this.$refs.fileUpload.removeEventListener('change', this.openFile);
- },
};
</script>
diff --git a/app/assets/javascripts/ide/components/repo_commit_section.vue b/app/assets/javascripts/ide/components/repo_commit_section.vue
index 979721dcb5a..5279417a72a 100644
--- a/app/assets/javascripts/ide/components/repo_commit_section.vue
+++ b/app/assets/javascripts/ide/components/repo_commit_section.vue
@@ -49,7 +49,9 @@ export default {
const createNewBranch = newBranch || this.startNewMR;
const payload = {
- branch: createNewBranch ? `${this.currentBranchId}-${new Date().getTime().toString()}` : this.currentBranchId,
+ branch: createNewBranch ?
+ `${this.currentBranchId}-${new Date().getTime().toString()}` :
+ this.currentBranchId,
commit_message: this.commitMessage,
actions: this.changedFiles.map(f => ({
action: f.tempFile ? 'create' : 'update',
@@ -103,69 +105,70 @@ export default {
</script>
<template>
-<div class="multi-file-commit-panel-section">
- <modal
- v-if="showNewBranchModal"
- :primary-button-label="__('Create new branch')"
- kind="primary"
- :title="__('Branch has changed')"
- :text="__('This branch has changed since you started editing. Would you like to create a new branch?')"
- @cancel="showNewBranchModal = false"
- @submit="makeCommit(true)"
- />
- <commit-files-list
- title="Staged"
- :file-list="changedFiles"
- :collapsed="rightPanelCollapsed"
- @toggleCollapsed="toggleCollapsed"
- />
- <form
- class="form-horizontal multi-file-commit-form"
- @submit.prevent="tryCommit"
- v-if="!rightPanelCollapsed"
- >
- <div class="multi-file-commit-fieldset">
- <textarea
- class="form-control multi-file-commit-message"
- name="commit-message"
- v-model="commitMessage"
- placeholder="Commit message"
- >
- </textarea>
- </div>
- <div class="multi-file-commit-fieldset">
- <label
- v-tooltip
- title="Create a new merge request with these changes"
- data-container="body"
- data-placement="top"
- >
- <input
- type="checkbox"
- v-model="startNewMR"
- />
- Merge Request
- </label>
- <button
- type="submit"
- :disabled="commitButtonDisabled"
- class="btn btn-default btn-sm append-right-10 prepend-left-10"
- >
- <i
- v-if="submitCommitsLoading"
- class="js-commit-loading-icon fa fa-spinner fa-spin"
- aria-hidden="true"
- aria-label="loading"
+ <div class="multi-file-commit-panel-section">
+ <modal
+ v-if="showNewBranchModal"
+ :primary-button-label="__('Create new branch')"
+ kind="primary"
+ :title="__('Branch has changed')"
+ :text="__(`This branch has changed since
+you started editing. Would you like to create a new branch?`)"
+ @cancel="showNewBranchModal = false"
+ @submit="makeCommit(true)"
+ />
+ <commit-files-list
+ title="Staged"
+ :file-list="changedFiles"
+ :collapsed="rightPanelCollapsed"
+ @toggleCollapsed="toggleCollapsed"
+ />
+ <form
+ class="form-horizontal multi-file-commit-form"
+ @submit.prevent="tryCommit"
+ v-if="!rightPanelCollapsed"
+ >
+ <div class="multi-file-commit-fieldset">
+ <textarea
+ class="form-control multi-file-commit-message"
+ name="commit-message"
+ v-model="commitMessage"
+ placeholder="Commit message"
>
- </i>
- Commit
- </button>
- <div
- class="multi-file-commit-message-count"
- >
- {{ commitMessageCount }}
+ </textarea>
</div>
- </div>
- </form>
-</div>
+ <div class="multi-file-commit-fieldset">
+ <label
+ v-tooltip
+ title="Create a new merge request with these changes"
+ data-container="body"
+ data-placement="top"
+ >
+ <input
+ type="checkbox"
+ v-model="startNewMR"
+ />
+ Merge Request
+ </label>
+ <button
+ type="submit"
+ :disabled="commitButtonDisabled"
+ class="btn btn-default btn-sm append-right-10 prepend-left-10"
+ >
+ <i
+ v-if="submitCommitsLoading"
+ class="js-commit-loading-icon fa fa-spinner fa-spin"
+ aria-hidden="true"
+ aria-label="loading"
+ >
+ </i>
+ Commit
+ </button>
+ <div
+ class="multi-file-commit-message-count"
+ >
+ {{ commitMessageCount }}
+ </div>
+ </div>
+ </form>
+ </div>
</template>
diff --git a/app/assets/javascripts/ide/components/repo_edit_button.vue b/app/assets/javascripts/ide/components/repo_edit_button.vue
index 42d5d709209..c43e9163340 100644
--- a/app/assets/javascripts/ide/components/repo_edit_button.vue
+++ b/app/assets/javascripts/ide/components/repo_edit_button.vue
@@ -40,7 +40,7 @@ export default {
aria-hidden="true">
</i>
<span>
- {{buttonLabel}}
+ {{ buttonLabel }}
</span>
</button>
<modal
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index 343fd0a5300..83b82ae44c9 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -6,6 +6,38 @@ import monacoLoader from '../monaco_loader';
import Editor from '../lib/editor';
export default {
+ computed: {
+ ...mapGetters([
+ 'activeFile',
+ 'activeFileExtension',
+ ]),
+ ...mapState([
+ 'leftPanelCollapsed',
+ 'rightPanelCollapsed',
+ 'panelResizing',
+ ]),
+ shouldHideEditor() {
+ return this.activeFile.binary && !this.activeFile.raw;
+ },
+ },
+ watch: {
+ activeFile(oldVal, newVal) {
+ if (newVal && !newVal.active) {
+ this.initMonaco();
+ }
+ },
+ leftPanelCollapsed() {
+ this.editor.updateDimensions();
+ },
+ rightPanelCollapsed() {
+ this.editor.updateDimensions();
+ },
+ panelResizing(isResizing) {
+ if (isResizing === false) {
+ this.editor.updateDimensions();
+ }
+ },
+ },
beforeDestroy() {
this.editor.dispose();
},
@@ -78,38 +110,6 @@ export default {
});
},
},
- watch: {
- activeFile(oldVal, newVal) {
- if (newVal && !newVal.active) {
- this.initMonaco();
- }
- },
- leftPanelCollapsed() {
- this.editor.updateDimensions();
- },
- rightPanelCollapsed() {
- this.editor.updateDimensions();
- },
- panelResizing(isResizing) {
- if (isResizing === false) {
- this.editor.updateDimensions();
- }
- },
- },
- computed: {
- ...mapGetters([
- 'activeFile',
- 'activeFileExtension',
- ]),
- ...mapState([
- 'leftPanelCollapsed',
- 'rightPanelCollapsed',
- 'panelResizing',
- ]),
- shouldHideEditor() {
- return this.activeFile.binary && !this.activeFile.raw;
- },
- },
};
</script>
diff --git a/app/assets/javascripts/ide/components/repo_file.vue b/app/assets/javascripts/ide/components/repo_file.vue
index c8b0441d81c..f7f4db89bdf 100644
--- a/app/assets/javascripts/ide/components/repo_file.vue
+++ b/app/assets/javascripts/ide/components/repo_file.vue
@@ -6,14 +6,14 @@
import fileIcon from '../../vue_shared/components/file_icon.vue';
export default {
- mixins: [
- timeAgoMixin,
- ],
components: {
skeletonLoadingContainer,
newDropdown,
fileIcon,
},
+ mixins: [
+ timeAgoMixin,
+ ],
props: {
file: {
type: Object,
@@ -60,6 +60,11 @@
};
},
},
+ updated() {
+ if (this.file.type === 'blob' && this.file.active) {
+ this.$el.scrollIntoView();
+ }
+ },
methods: {
clickFile(row) {
// Manual Action if a tree is selected/opened
@@ -72,11 +77,6 @@
this.$router.push(`/project${row.url}`);
},
},
- updated() {
- if (this.file.type === 'blob' && this.file.active) {
- this.$el.scrollIntoView();
- }
- },
};
</script>
@@ -99,8 +99,7 @@
:opened="file.opened"
:style="levelIndentation"
:size="16"
- >
- </file-icon>
+ />
{{ file.name }}
</a>
<new-dropdown
@@ -108,7 +107,8 @@
:project-id="file.projectId"
:branch="file.branchId"
:path="file.path"
- :parent="file"/>
+ :parent="file"
+ />
<i
class="fa"
v-if="changedClass"
diff --git a/app/assets/javascripts/ide/components/repo_file_buttons.vue b/app/assets/javascripts/ide/components/repo_file_buttons.vue
index 34f0d51819a..aabc0d8eada 100644
--- a/app/assets/javascripts/ide/components/repo_file_buttons.vue
+++ b/app/assets/javascripts/ide/components/repo_file_buttons.vue
@@ -35,20 +35,24 @@ export default {
<div
class="btn-group"
role="group"
- aria-label="File actions">
+ aria-label="File actions"
+ >
<a
:href="activeFile.blamePath"
- class="btn btn-default btn-sm blame">
+ class="btn btn-default btn-sm blame"
+ >
Blame
</a>
<a
:href="activeFile.commitsPath"
- class="btn btn-default btn-sm history">
+ class="btn btn-default btn-sm history"
+ >
History
</a>
<a
:href="activeFile.permalink"
- class="btn btn-default btn-sm permalink">
+ class="btn btn-default btn-sm permalink"
+ >
Permalink
</a>
</div>
diff --git a/app/assets/javascripts/ide/components/repo_loading_file.vue b/app/assets/javascripts/ide/components/repo_loading_file.vue
index 7eb840c7608..3aeb6f0b28f 100644
--- a/app/assets/javascripts/ide/components/repo_loading_file.vue
+++ b/app/assets/javascripts/ide/components/repo_loading_file.vue
@@ -25,15 +25,13 @@
/>
</td>
<template v-if="!leftPanelCollapsed">
- <td
- class="hidden-sm hidden-xs">
+ <td class="hidden-sm hidden-xs">
<skeleton-loading-container
:small="true"
/>
</td>
- <td
- class="hidden-xs">
+ <td class="hidden-xs">
<skeleton-loading-container
class="animation-container-right"
:small="true"
diff --git a/app/assets/javascripts/ide/components/repo_preview.vue b/app/assets/javascripts/ide/components/repo_preview.vue
index 3d1e0297bd5..e47270a9855 100644
--- a/app/assets/javascripts/ide/components/repo_preview.vue
+++ b/app/assets/javascripts/ide/components/repo_preview.vue
@@ -1,65 +1,71 @@
<script>
-import { mapGetters } from 'vuex';
-import LineHighlighter from '../../line_highlighter';
-import syntaxHighlight from '../../syntax_highlight';
+ import { mapGetters } from 'vuex';
+ import LineHighlighter from '../../line_highlighter';
+ import syntaxHighlight from '../../syntax_highlight';
-export default {
- computed: {
- ...mapGetters([
- 'activeFile',
- ]),
- renderErrorTooLarge() {
- return this.activeFile.renderError === 'too_large';
+ export default {
+ computed: {
+ ...mapGetters([
+ 'activeFile',
+ ]),
+ renderErrorTooLarge() {
+ return this.activeFile.renderError === 'too_large';
+ },
},
- },
- methods: {
- highlightFile() {
- syntaxHighlight($(this.$el).find('.file-content'));
- },
- },
- mounted() {
- this.highlightFile();
- this.lineHighlighter = new LineHighlighter({
- fileHolderSelector: '.blob-viewer-container',
- scrollFileHolder: true,
- });
- },
- updated() {
- this.$nextTick(() => {
+ mounted() {
this.highlightFile();
- });
- },
-};
+ this.lineHighlighter = new LineHighlighter({
+ fileHolderSelector: '.blob-viewer-container',
+ scrollFileHolder: true,
+ });
+ },
+ updated() {
+ this.$nextTick(() => {
+ this.highlightFile();
+ });
+ },
+ methods: {
+ highlightFile() {
+ syntaxHighlight($(this.$el).find('.file-content'));
+ },
+ },
+ };
</script>
<template>
-<div>
- <div
- v-if="!activeFile.renderError"
- v-html="activeFile.html"
- class="multi-file-preview-holder"
- >
- </div>
- <div
- v-else-if="activeFile.tempFile"
- class="vertical-center render-error">
- <p class="text-center">
- The source could not be displayed for this temporary file.
- </p>
- </div>
- <div
- v-else-if="renderErrorTooLarge"
- class="vertical-center render-error">
- <p class="text-center">
- The source could not be displayed because it is too large. You can <a :href="activeFile.rawPath" download>download</a> it instead.
- </p>
- </div>
- <div
- v-else
- class="vertical-center render-error">
- <p class="text-center">
- The source could not be displayed because a rendering error occurred. You can <a :href="activeFile.rawPath" download>download</a> it instead.
- </p>
+ <div>
+ <div
+ v-if="!activeFile.renderError"
+ v-html="activeFile.html"
+ class="multi-file-preview-holder"
+ >
+ </div>
+ <div
+ v-else-if="activeFile.tempFile"
+ class="vertical-center render-error">
+ <p class="text-center">
+ The source could not be displayed for this temporary file.
+ </p>
+ </div>
+ <div
+ v-else-if="renderErrorTooLarge"
+ class="vertical-center render-error">
+ <p class="text-center">
+ The source could not be displayed because it is too large.
+ You can <a
+ :href="activeFile.rawPath"
+ download>download</a> it instead.
+ </p>
+ </div>
+ <div
+ v-else
+ class="vertical-center render-error">
+ <p class="text-center">
+ The source could not be displayed because a rendering error occurred.
+ You can <a
+ :href="activeFile.rawPath"
+ download>download</a> it instead.
+ </p>
+ </div>
</div>
-</div>
</template>
diff --git a/app/assets/javascripts/ide/components/repo_tab.vue b/app/assets/javascripts/ide/components/repo_tab.vue
index e7684884b2c..5ed7bddf6ae 100644
--- a/app/assets/javascripts/ide/components/repo_tab.vue
+++ b/app/assets/javascripts/ide/components/repo_tab.vue
@@ -1,48 +1,46 @@
<script>
-import { mapActions } from 'vuex';
-import fileIcon from '../../vue_shared/components/file_icon.vue';
+ import { mapActions } from 'vuex';
+ import fileIcon from '../../vue_shared/components/file_icon.vue';
-export default {
- props: {
- tab: {
- type: Object,
- required: true,
+ export default {
+ components: {
+ fileIcon,
},
- },
- components: {
- fileIcon,
- },
- computed: {
- closeLabel() {
- if (this.tab.changed || this.tab.tempFile) {
- return `${this.tab.name} changed`;
- }
- return `Close ${this.tab.name}`;
+ props: {
+ tab: {
+ type: Object,
+ required: true,
+ },
},
- changedClass() {
- const tabChangedObj = {
- 'fa-times close-icon': !this.tab.changed && !this.tab.tempFile,
- 'fa-circle unsaved-icon': this.tab.changed || this.tab.tempFile,
- };
- return tabChangedObj;
+ computed: {
+ closeLabel() {
+ if (this.tab.changed || this.tab.tempFile) {
+ return `${this.tab.name} changed`;
+ }
+ return `Close ${this.tab.name}`;
+ },
+ changedClass() {
+ const tabChangedObj = {
+ 'fa-times close-icon': !this.tab.changed && !this.tab.tempFile,
+ 'fa-circle unsaved-icon': this.tab.changed || this.tab.tempFile,
+ };
+ return tabChangedObj;
+ },
},
- },
- methods: {
- ...mapActions([
- 'closeFile',
- ]),
- clickFile(tab) {
- this.$router.push(`/project${tab.url}`);
+ methods: {
+ ...mapActions([
+ 'closeFile',
+ ]),
+ clickFile(tab) {
+ this.$router.push(`/project${tab.url}`);
+ },
},
- },
-};
+ };
</script>
<template>
- <li
- @click="clickFile(tab)"
- >
+ <li @click="clickFile(tab)">
<button
type="button"
class="multi-file-tab-close"
@@ -69,8 +67,7 @@ export default {
<file-icon
:file-name="tab.name"
:size="16"
- >
- </file-icon>
+ />
{{ tab.name }}
</div>
</li>
diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue
index fc10a43d1bf..f85d66e9b1d 100644
--- a/app/assets/javascripts/issue_show/components/app.vue
+++ b/app/assets/javascripts/issue_show/components/app.vue
@@ -1,308 +1,306 @@
<script>
-import Visibility from 'visibilityjs';
-import { visitUrl } from '../../lib/utils/url_utility';
-import Poll from '../../lib/utils/poll';
-import eventHub from '../event_hub';
-import Service from '../services/index';
-import Store from '../stores';
-import titleComponent from './title.vue';
-import descriptionComponent from './description.vue';
-import editedComponent from './edited.vue';
-import formComponent from './form.vue';
-import recaptchaModalImplementor from '../../vue_shared/mixins/recaptcha_modal_implementor';
+ import Visibility from 'visibilityjs';
+ import { visitUrl } from '../../lib/utils/url_utility';
+ import Poll from '../../lib/utils/poll';
+ import eventHub from '../event_hub';
+ import Service from '../services/index';
+ import Store from '../stores';
+ import titleComponent from './title.vue';
+ import descriptionComponent from './description.vue';
+ import editedComponent from './edited.vue';
+ import formComponent from './form.vue';
+ import recaptchaModalImplementor from '../../vue_shared/mixins/recaptcha_modal_implementor';
-export default {
- props: {
- endpoint: {
- required: true,
- type: String,
+ export default {
+ components: {
+ descriptionComponent,
+ titleComponent,
+ editedComponent,
+ formComponent,
},
- updateEndpoint: {
- required: true,
- type: String,
- },
- canUpdate: {
- required: true,
- type: Boolean,
- },
- canDestroy: {
- required: true,
- type: Boolean,
- },
- showInlineEditButton: {
- type: Boolean,
- required: false,
- default: true,
- },
- showDeleteButton: {
- type: Boolean,
- required: false,
- default: true,
- },
- enableAutocomplete: {
- type: Boolean,
- required: false,
- default: true,
- },
- issuableRef: {
- type: String,
- required: true,
- },
- initialTitleHtml: {
- type: String,
- required: true,
- },
- initialTitleText: {
- type: String,
- required: true,
- },
- initialDescriptionHtml: {
- type: String,
- required: false,
- default: '',
- },
- initialDescriptionText: {
- type: String,
- required: false,
- default: '',
- },
- initialTaskStatus: {
- type: String,
- required: false,
- default: '',
- },
- updatedAt: {
- type: String,
- required: false,
- default: '',
- },
- updatedByName: {
- type: String,
- required: false,
- default: '',
- },
- updatedByPath: {
- type: String,
- required: false,
- default: '',
- },
- issuableTemplates: {
- type: Array,
- required: false,
- default: () => [],
- },
- markdownPreviewPath: {
- type: String,
- required: true,
- },
- markdownDocsPath: {
- type: String,
- required: true,
- },
- projectPath: {
- type: String,
- required: true,
- },
- projectNamespace: {
- type: String,
- required: true,
- },
- issuableType: {
- type: String,
- required: false,
- default: 'issue',
- },
- canAttachFile: {
- type: Boolean,
- required: false,
- default: true,
+ mixins: [
+ recaptchaModalImplementor,
+ ],
+ props: {
+ endpoint: {
+ required: true,
+ type: String,
+ },
+ updateEndpoint: {
+ required: true,
+ type: String,
+ },
+ canUpdate: {
+ required: true,
+ type: Boolean,
+ },
+ canDestroy: {
+ required: true,
+ type: Boolean,
+ },
+ showInlineEditButton: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ showDeleteButton: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ enableAutocomplete: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ issuableRef: {
+ type: String,
+ required: true,
+ },
+ initialTitleHtml: {
+ type: String,
+ required: true,
+ },
+ initialTitleText: {
+ type: String,
+ required: true,
+ },
+ initialDescriptionHtml: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ initialDescriptionText: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ initialTaskStatus: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ updatedAt: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ updatedByName: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ updatedByPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ issuableTemplates: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ markdownPreviewPath: {
+ type: String,
+ required: true,
+ },
+ markdownDocsPath: {
+ type: String,
+ required: true,
+ },
+ projectPath: {
+ type: String,
+ required: true,
+ },
+ projectNamespace: {
+ type: String,
+ required: true,
+ },
+ issuableType: {
+ type: String,
+ required: false,
+ default: 'issue',
+ },
+ canAttachFile: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
},
- },
- data() {
- const store = new Store({
- titleHtml: this.initialTitleHtml,
- titleText: this.initialTitleText,
- descriptionHtml: this.initialDescriptionHtml,
- descriptionText: this.initialDescriptionText,
- updatedAt: this.updatedAt,
- updatedByName: this.updatedByName,
- updatedByPath: this.updatedByPath,
- taskStatus: this.initialTaskStatus,
- });
+ data() {
+ const store = new Store({
+ titleHtml: this.initialTitleHtml,
+ titleText: this.initialTitleText,
+ descriptionHtml: this.initialDescriptionHtml,
+ descriptionText: this.initialDescriptionText,
+ updatedAt: this.updatedAt,
+ updatedByName: this.updatedByName,
+ updatedByPath: this.updatedByPath,
+ taskStatus: this.initialTaskStatus,
+ });
- return {
- store,
- state: store.state,
- showForm: false,
- };
- },
- computed: {
- formState() {
- return this.store.formState;
+ return {
+ store,
+ state: store.state,
+ showForm: false,
+ };
},
- hasUpdated() {
- return !!this.state.updatedAt;
+ computed: {
+ formState() {
+ return this.store.formState;
+ },
+ hasUpdated() {
+ return !!this.state.updatedAt;
+ },
},
- },
- components: {
- descriptionComponent,
- titleComponent,
- editedComponent,
- formComponent,
- },
-
- mixins: [
- recaptchaModalImplementor,
- ],
+ created() {
+ this.service = new Service(this.endpoint);
+ this.poll = new Poll({
+ resource: this.service,
+ method: 'getData',
+ successCallback: res => this.store.updateState(res.data),
+ errorCallback(err) {
+ throw new Error(err);
+ },
+ });
- methods: {
- openForm() {
- if (!this.showForm) {
- this.showForm = true;
- this.store.setFormState({
- title: this.state.titleText,
- description: this.state.descriptionText,
- lockedWarningVisible: false,
- updateLoading: false,
- });
+ if (!Visibility.hidden()) {
+ this.poll.makeRequest();
}
- },
- closeForm() {
- this.showForm = false;
- },
-
- updateIssuable() {
- return this.service.updateIssuable(this.store.formState)
- .then(res => res.data)
- .then(data => this.checkForSpam(data))
- .then((data) => {
- if (location.pathname !== data.web_url) {
- visitUrl(data.web_url);
- }
-
- return this.service.getData();
- })
- .then(res => res.data)
- .then((data) => {
- this.store.updateState(data);
- eventHub.$emit('close.form');
- })
- .catch((error) => {
- if (error && error.name === 'SpamError') {
- this.openRecaptcha();
- } else {
- eventHub.$emit('close.form');
- window.Flash(`Error updating ${this.issuableType}`);
- }
- });
- },
- closeRecaptchaModal() {
- this.store.setFormState({
- updateLoading: false,
+ Visibility.change(() => {
+ if (!Visibility.hidden()) {
+ this.poll.restart();
+ } else {
+ this.poll.stop();
+ }
});
- this.closeRecaptcha();
+ eventHub.$on('delete.issuable', this.deleteIssuable);
+ eventHub.$on('update.issuable', this.updateIssuable);
+ eventHub.$on('close.form', this.closeForm);
+ eventHub.$on('open.form', this.openForm);
+ },
+ beforeDestroy() {
+ eventHub.$off('delete.issuable', this.deleteIssuable);
+ eventHub.$off('update.issuable', this.updateIssuable);
+ eventHub.$off('close.form', this.closeForm);
+ eventHub.$off('open.form', this.openForm);
},
+ methods: {
+ openForm() {
+ if (!this.showForm) {
+ this.showForm = true;
+ this.store.setFormState({
+ title: this.state.titleText,
+ description: this.state.descriptionText,
+ lockedWarningVisible: false,
+ updateLoading: false,
+ });
+ }
+ },
+ closeForm() {
+ this.showForm = false;
+ },
- deleteIssuable() {
- this.service.deleteIssuable()
- .then(res => res.data)
- .then((data) => {
- // Stop the poll so we don't get 404's with the issuable not existing
- this.poll.stop();
+ updateIssuable() {
+ return this.service.updateIssuable(this.store.formState)
+ .then(res => res.data)
+ .then(data => this.checkForSpam(data))
+ .then((data) => {
+ if (location.pathname !== data.web_url) {
+ visitUrl(data.web_url);
+ }
- visitUrl(data.web_url);
- })
- .catch(() => {
- eventHub.$emit('close.form');
- window.Flash(`Error deleting ${this.issuableType}`);
- });
- },
- },
- created() {
- this.service = new Service(this.endpoint);
- this.poll = new Poll({
- resource: this.service,
- method: 'getData',
- successCallback: res => this.store.updateState(res.data),
- errorCallback(err) {
- throw new Error(err);
+ return this.service.getData();
+ })
+ .then(res => res.data)
+ .then((data) => {
+ this.store.updateState(data);
+ eventHub.$emit('close.form');
+ })
+ .catch((error) => {
+ if (error && error.name === 'SpamError') {
+ this.openRecaptcha();
+ } else {
+ eventHub.$emit('close.form');
+ window.Flash(`Error updating ${this.issuableType}`);
+ }
+ });
},
- });
- if (!Visibility.hidden()) {
- this.poll.makeRequest();
- }
+ closeRecaptchaModal() {
+ this.store.setFormState({
+ updateLoading: false,
+ });
+
+ this.closeRecaptcha();
+ },
- Visibility.change(() => {
- if (!Visibility.hidden()) {
- this.poll.restart();
- } else {
- this.poll.stop();
- }
- });
+ deleteIssuable() {
+ this.service.deleteIssuable()
+ .then(res => res.data)
+ .then((data) => {
+ // Stop the poll so we don't get 404's with the issuable not existing
+ this.poll.stop();
- eventHub.$on('delete.issuable', this.deleteIssuable);
- eventHub.$on('update.issuable', this.updateIssuable);
- eventHub.$on('close.form', this.closeForm);
- eventHub.$on('open.form', this.openForm);
- },
- beforeDestroy() {
- eventHub.$off('delete.issuable', this.deleteIssuable);
- eventHub.$off('update.issuable', this.updateIssuable);
- eventHub.$off('close.form', this.closeForm);
- eventHub.$off('open.form', this.openForm);
- },
-};
+ visitUrl(data.web_url);
+ })
+ .catch(() => {
+ eventHub.$emit('close.form');
+ window.Flash(`Error deleting ${this.issuableType}`);
+ });
+ },
+ },
+ };
</script>
<template>
-<div>
- <div v-if="canUpdate && showForm">
- <form-component
- :form-state="formState"
- :can-destroy="canDestroy"
- :issuable-templates="issuableTemplates"
- :markdown-docs-path="markdownDocsPath"
- :markdown-preview-path="markdownPreviewPath"
- :project-path="projectPath"
- :project-namespace="projectNamespace"
- :show-delete-button="showDeleteButton"
- :can-attach-file="canAttachFile"
- :enable-autocomplete="enableAutocomplete"
- />
+ <div>
+ <div v-if="canUpdate && showForm">
+ <form-component
+ :form-state="formState"
+ :can-destroy="canDestroy"
+ :issuable-templates="issuableTemplates"
+ :markdown-docs-path="markdownDocsPath"
+ :markdown-preview-path="markdownPreviewPath"
+ :project-path="projectPath"
+ :project-namespace="projectNamespace"
+ :show-delete-button="showDeleteButton"
+ :can-attach-file="canAttachFile"
+ :enable-autocomplete="enableAutocomplete"
+ />
- <recaptcha-modal
- v-show="showRecaptcha"
- :html="recaptchaHTML"
- @close="closeRecaptchaModal"
- />
- </div>
- <div v-else>
- <title-component
- :issuable-ref="issuableRef"
- :can-update="canUpdate"
- :title-html="state.titleHtml"
- :title-text="state.titleText"
- :show-inline-edit-button="showInlineEditButton"
- />
- <description-component
- v-if="state.descriptionHtml"
- :can-update="canUpdate"
- :description-html="state.descriptionHtml"
- :description-text="state.descriptionText"
- :updated-at="state.updatedAt"
- :task-status="state.taskStatus"
- :issuable-type="issuableType"
- :update-url="updateEndpoint"
- />
- <edited-component
- v-if="hasUpdated"
- :updated-at="state.updatedAt"
- :updated-by-name="state.updatedByName"
- :updated-by-path="state.updatedByPath"
- />
+ <recaptcha-modal
+ v-show="showRecaptcha"
+ :html="recaptchaHTML"
+ @close="closeRecaptchaModal"
+ />
+ </div>
+ <div v-else>
+ <title-component
+ :issuable-ref="issuableRef"
+ :can-update="canUpdate"
+ :title-html="state.titleHtml"
+ :title-text="state.titleText"
+ :show-inline-edit-button="showInlineEditButton"
+ />
+ <description-component
+ v-if="state.descriptionHtml"
+ :can-update="canUpdate"
+ :description-html="state.descriptionHtml"
+ :description-text="state.descriptionText"
+ :updated-at="state.updatedAt"
+ :task-status="state.taskStatus"
+ :issuable-type="issuableType"
+ :update-url="updateEndpoint"
+ />
+ <edited-component
+ v-if="hasUpdated"
+ :updated-at="state.updatedAt"
+ :updated-by-name="state.updatedByName"
+ :updated-by-path="state.updatedByPath"
+ />
+ </div>
</div>
-</div>
</template>
diff --git a/app/assets/javascripts/issue_show/components/description.vue b/app/assets/javascripts/issue_show/components/description.vue
index c3f2bf130bb..9afa9dea126 100644
--- a/app/assets/javascripts/issue_show/components/description.vue
+++ b/app/assets/javascripts/issue_show/components/description.vue
@@ -56,7 +56,10 @@
this.updateTaskStatusText();
},
},
-
+ mounted() {
+ this.renderGFM();
+ this.updateTaskStatusText();
+ },
methods: {
renderGFM() {
$(this.$refs['gfm-content']).renderGFM();
@@ -88,17 +91,17 @@
if (taskRegexMatches) {
$tasks.text(this.taskStatus);
- $tasksShort.text(`${taskRegexMatches[1]}/${taskRegexMatches[2]} task${taskRegexMatches[2] > 1 ? 's' : ''}`);
+ $tasksShort.text(
+ `${taskRegexMatches[1]}/${taskRegexMatches[2]} task${taskRegexMatches[2] > 1 ?
+ 's' :
+ ''}`,
+ );
} else {
$tasks.text('');
$tasksShort.text('');
}
},
},
- mounted() {
- this.renderGFM();
- this.updateTaskStatusText();
- },
};
</script>
@@ -108,7 +111,8 @@
class="description"
:class="{
'js-task-list-container': canUpdate
- }">
+ }"
+ >
<div
class="wiki"
:class="{
diff --git a/app/assets/javascripts/issue_show/components/edited.vue b/app/assets/javascripts/issue_show/components/edited.vue
index 992b7064c13..01097b5b35e 100644
--- a/app/assets/javascripts/issue_show/components/edited.vue
+++ b/app/assets/javascripts/issue_show/components/edited.vue
@@ -1,33 +1,33 @@
<script>
-import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
+ import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
-export default {
- props: {
- updatedAt: {
- type: String,
- required: false,
- default: '',
+ export default {
+ components: {
+ timeAgoTooltip,
},
- updatedByName: {
- type: String,
- required: false,
- default: '',
+ props: {
+ updatedAt: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ updatedByName: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ updatedByPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
- updatedByPath: {
- type: String,
- required: false,
- default: '',
+ computed: {
+ hasUpdatedBy() {
+ return this.updatedByName && this.updatedByPath;
+ },
},
- },
- components: {
- timeAgoTooltip,
- },
- computed: {
- hasUpdatedBy() {
- return this.updatedByName && this.updatedByPath;
- },
- },
-};
+ };
</script>
<template>
@@ -48,7 +48,7 @@ export default {
class="author_link"
:href="updatedByPath"
>
- <span>{{updatedByName}}</span>
+ <span>{{ updatedByName }}</span>
</a>
</span>
</small>
diff --git a/app/assets/javascripts/issue_show/components/fields/description.vue b/app/assets/javascripts/issue_show/components/fields/description.vue
index 4e577546551..d9fa2764d65 100644
--- a/app/assets/javascripts/issue_show/components/fields/description.vue
+++ b/app/assets/javascripts/issue_show/components/fields/description.vue
@@ -3,6 +3,9 @@
import markdownField from '../../../vue_shared/components/markdown/field.vue';
export default {
+ components: {
+ markdownField,
+ },
mixins: [updateMixin],
props: {
formState: {
@@ -28,9 +31,6 @@
default: true,
},
},
- components: {
- markdownField,
- },
mounted() {
this.$refs.textarea.focus();
},
diff --git a/app/assets/javascripts/issue_show/components/form.vue b/app/assets/javascripts/issue_show/components/form.vue
index 0fa19022336..779705e19ac 100644
--- a/app/assets/javascripts/issue_show/components/form.vue
+++ b/app/assets/javascripts/issue_show/components/form.vue
@@ -6,6 +6,13 @@
import descriptionTemplate from './fields/description_template.vue';
export default {
+ components: {
+ lockedWarning,
+ titleField,
+ descriptionField,
+ descriptionTemplate,
+ editActions,
+ },
props: {
canDestroy: {
type: Boolean,
@@ -52,13 +59,6 @@
default: true,
},
},
- components: {
- lockedWarning,
- titleField,
- descriptionField,
- descriptionTemplate,
- editActions,
- },
computed: {
hasIssuableTemplates() {
return this.issuableTemplates.length;
@@ -78,16 +78,19 @@
:form-state="formState"
:issuable-templates="issuableTemplates"
:project-path="projectPath"
- :project-namespace="projectNamespace" />
+ :project-namespace="projectNamespace"
+ />
</div>
<div
:class="{
'col-sm-8 col-lg-9': hasIssuableTemplates,
'col-xs-12': !hasIssuableTemplates,
- }">
+ }"
+ >
<title-field
:form-state="formState"
- :issuable-templates="issuableTemplates" />
+ :issuable-templates="issuableTemplates"
+ />
</div>
</div>
<description-field
@@ -100,6 +103,7 @@
<edit-actions
:form-state="formState"
:can-destroy="canDestroy"
- :show-delete-button="showDeleteButton" />
+ :show-delete-button="showDeleteButton"
+ />
</form>
</template>
diff --git a/app/assets/javascripts/issue_show/components/title.vue b/app/assets/javascripts/issue_show/components/title.vue
index b7e6eadd440..aec890a2ff6 100644
--- a/app/assets/javascripts/issue_show/components/title.vue
+++ b/app/assets/javascripts/issue_show/components/title.vue
@@ -5,14 +5,10 @@
import { spriteIcon } from '../../lib/utils/common_utils';
export default {
- mixins: [animateMixin],
- data() {
- return {
- preAnimation: false,
- pulseAnimation: false,
- titleEl: document.querySelector('title'),
- };
+ directives: {
+ tooltip,
},
+ mixins: [animateMixin],
props: {
issuableRef: {
type: String,
@@ -37,8 +33,17 @@
default: false,
},
},
- directives: {
- tooltip,
+ data() {
+ return {
+ preAnimation: false,
+ pulseAnimation: false,
+ titleEl: document.querySelector('title'),
+ };
+ },
+ computed: {
+ pencilIcon() {
+ return spriteIcon('pencil', 'link-highlight');
+ },
},
watch: {
titleHtml() {
@@ -46,11 +51,6 @@
this.animateChange();
},
},
- computed: {
- pencilIcon() {
- return spriteIcon('pencil', 'link-highlight');
- },
- },
methods: {
setPageTitle() {
const currentPageTitleScope = this.titleEl.innerText.split('·');
@@ -85,7 +85,7 @@
data-placement="bottom"
data-container="body"
@click="edit"
- >
+ >
</button>
</div>
</template>
diff --git a/app/assets/javascripts/jobs/components/header.vue b/app/assets/javascripts/jobs/components/header.vue
index c660828b30e..9e3f659db5f 100644
--- a/app/assets/javascripts/jobs/components/header.vue
+++ b/app/assets/javascripts/jobs/components/header.vue
@@ -3,7 +3,11 @@
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
export default {
- name: 'jobHeaderSection',
+ name: 'JobHeaderSection',
+ components: {
+ ciHeader,
+ loadingIcon,
+ },
props: {
job: {
type: Object,
@@ -14,10 +18,6 @@
required: true,
},
},
- components: {
- ciHeader,
- loadingIcon,
- },
data() {
return {
actions: this.getActions(),
@@ -34,6 +34,11 @@
return this.job.started;
},
},
+ watch: {
+ job() {
+ this.actions = this.getActions();
+ },
+ },
methods: {
getActions() {
const actions = [];
@@ -49,11 +54,6 @@
return actions;
},
},
- watch: {
- job() {
- this.actions = this.getActions();
- },
- },
};
</script>
<template>
@@ -72,6 +72,6 @@
<loading-icon
v-if="isLoading"
size="2"
- />
+ />
</div>
</template>
diff --git a/app/assets/javascripts/jobs/components/sidebar_detail_row.vue b/app/assets/javascripts/jobs/components/sidebar_detail_row.vue
index ab2bcd728a8..a6819aaeb12 100644
--- a/app/assets/javascripts/jobs/components/sidebar_detail_row.vue
+++ b/app/assets/javascripts/jobs/components/sidebar_detail_row.vue
@@ -23,9 +23,10 @@
<p class="build-detail-row">
<span
v-if="hasTitle"
- class="build-light-text">
- {{title}}:
+ class="build-light-text"
+ >
+ {{ title }}:
</span>
- {{value}}
+ {{ value }}
</p>
</template>
diff --git a/app/assets/javascripts/jobs/components/sidebar_details_block.vue b/app/assets/javascripts/jobs/components/sidebar_details_block.vue
index d0145fed396..56814a52525 100644
--- a/app/assets/javascripts/jobs/components/sidebar_details_block.vue
+++ b/app/assets/javascripts/jobs/components/sidebar_details_block.vue
@@ -6,6 +6,13 @@
export default {
name: 'SidebarDetailsBlock',
+ components: {
+ detailRow,
+ loadingIcon,
+ },
+ mixins: [
+ timeagoMixin,
+ ],
props: {
job: {
type: Object,
@@ -16,13 +23,6 @@
required: true,
},
},
- mixins: [
- timeagoMixin,
- ],
- components: {
- detailRow,
- loadingIcon,
- },
computed: {
shouldRenderContent() {
return !this.isLoading && Object.keys(this.job).length > 0;
@@ -58,11 +58,13 @@
<template v-if="shouldRenderContent">
<div
class="block retry-link"
- v-if="job.retry_path || job.new_issue_path">
+ v-if="job.retry_path || job.new_issue_path"
+ >
<a
v-if="job.new_issue_path"
class="js-new-issue btn btn-new btn-inverted"
- :href="job.new_issue_path">
+ :href="job.new_issue_path"
+ >
New issue
</a>
<a
@@ -70,20 +72,21 @@
class="js-retry-job btn btn-inverted-secondary"
:href="job.retry_path"
data-method="post"
- rel="nofollow">
+ rel="nofollow"
+ >
Retry
</a>
</div>
<div :class="{block : renderBlock }">
<p
class="build-detail-row js-job-mr"
- v-if="job.merge_request">
- <span
- class="build-light-text">
+ v-if="job.merge_request"
+ >
+ <span class="build-light-text">
Merge Request:
</span>
<a :href="job.merge_request.path">
- !{{job.merge_request.iid}}
+ !{{ job.merge_request.iid }}
</a>
</p>
@@ -92,49 +95,49 @@
v-if="job.duration"
title="Duration"
:value="duration"
- />
+ />
<detail-row
class="js-job-finished"
v-if="job.finished_at"
title="Finished"
:value="timeFormated(job.finished_at)"
- />
+ />
<detail-row
class="js-job-erased"
v-if="job.erased_at"
title="Erased"
:value="timeFormated(job.erased_at)"
- />
+ />
<detail-row
class="js-job-queued"
v-if="job.queued"
title="Queued"
:value="queued"
- />
+ />
<detail-row
class="js-job-runner"
v-if="job.runner"
title="Runner"
:value="runnerId"
- />
+ />
<detail-row
class="js-job-coverage"
v-if="job.coverage"
title="Coverage"
:value="coverage"
- />
+ />
<p
class="build-detail-row js-job-tags"
- v-if="job.tags.length">
- <span
- class="build-light-text">
+ v-if="job.tags.length"
+ >
+ <span class="build-light-text">
Tags:
</span>
<span
- v-for="tag in job.tags"
- key="tag"
+ v-for="(tag, i) in job.tags"
+ :key="i"
class="label label-primary">
- {{tag}}
+ {{ tag }}
</span>
</p>
@@ -146,7 +149,8 @@
class="js-cancel-job btn btn-sm btn-default"
:href="job.cancel_path"
data-method="post"
- rel="nofollow">
+ rel="nofollow"
+ >
Cancel
</a>
</div>
@@ -156,6 +160,6 @@
class="prepend-top-10"
v-if="isLoading"
size="2"
- />
+ />
</div>
</template>
diff --git a/app/assets/javascripts/jobs/job_details_bundle.js b/app/assets/javascripts/jobs/job_details_bundle.js
index baaf5641200..db53b04de0e 100644
--- a/app/assets/javascripts/jobs/job_details_bundle.js
+++ b/app/assets/javascripts/jobs/job_details_bundle.js
@@ -13,14 +13,14 @@ document.addEventListener('DOMContentLoaded', () => {
// eslint-disable-next-line no-new
new Vue({
el: '#js-build-header-vue',
+ components: {
+ jobHeader,
+ },
data() {
return {
mediator,
};
},
- components: {
- jobHeader,
- },
mounted() {
this.mediator.initBuildClass();
},
@@ -38,14 +38,14 @@ document.addEventListener('DOMContentLoaded', () => {
// eslint-disable-next-line
new Vue({
el: '#js-details-block-vue',
+ components: {
+ detailsBlock,
+ },
data() {
return {
mediator,
};
},
- components: {
- detailsBlock,
- },
render(createElement) {
return createElement('details-block', {
props: {
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
index 94561d6b7c3..792b7523889 100644
--- a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
+++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
@@ -25,12 +25,12 @@ $(() => {
gl.MergeConflictsResolverApp = new Vue({
el: '#conflicts',
- data: mergeConflictsStore.state,
components: {
'diff-file-editor': gl.mergeConflicts.diffFileEditor,
'inline-conflict-lines': gl.mergeConflicts.inlineConflictLines,
'parallel-conflict-lines': gl.mergeConflicts.parallelConflictLines
},
+ data: mergeConflictsStore.state,
computed: {
conflictsCountText() { return mergeConflictsStore.getConflictsCountText(); },
readyToCommit() { return mergeConflictsStore.isReadyToCommit(); },
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index 8da723ced03..025e38ea99a 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -11,6 +11,12 @@
export default {
+ components: {
+ Graph,
+ GraphGroup,
+ EmptyState,
+ },
+
data() {
const metricsData = document.querySelector('#prometheus-graphs').dataset;
const store = new MonitoringStore();
@@ -36,12 +42,30 @@
};
},
- components: {
- Graph,
- GraphGroup,
- EmptyState,
+ created() {
+ this.service = new MonitoringService({
+ metricsEndpoint: this.metricsEndpoint,
+ deploymentEndpoint: this.deploymentEndpoint,
+ });
+ eventHub.$on('toggleAspectRatio', this.toggleAspectRatio);
+ eventHub.$on('hoverChanged', this.hoverChanged);
+ },
+
+ beforeDestroy() {
+ eventHub.$off('toggleAspectRatio', this.toggleAspectRatio);
+ eventHub.$off('hoverChanged', this.hoverChanged);
+ window.removeEventListener('resize', this.resizeThrottled, false);
},
+ mounted() {
+ this.resizeThrottled = _.throttle(this.resize, 600);
+ if (!this.hasMetrics) {
+ this.state = 'gettingStarted';
+ } else {
+ this.getGraphsData();
+ window.addEventListener('resize', this.resizeThrottled, false);
+ }
+ },
methods: {
getGraphsData() {
this.state = 'loading';
@@ -72,36 +96,14 @@
this.hoverData = data;
},
},
-
- created() {
- this.service = new MonitoringService({
- metricsEndpoint: this.metricsEndpoint,
- deploymentEndpoint: this.deploymentEndpoint,
- });
- eventHub.$on('toggleAspectRatio', this.toggleAspectRatio);
- eventHub.$on('hoverChanged', this.hoverChanged);
- },
-
- beforeDestroy() {
- eventHub.$off('toggleAspectRatio', this.toggleAspectRatio);
- eventHub.$off('hoverChanged', this.hoverChanged);
- window.removeEventListener('resize', this.resizeThrottled, false);
- },
-
- mounted() {
- this.resizeThrottled = _.throttle(this.resize, 600);
- if (!this.hasMetrics) {
- this.state = 'gettingStarted';
- } else {
- this.getGraphsData();
- window.addEventListener('resize', this.resizeThrottled, false);
- }
- },
};
</script>
<template>
- <div v-if="!showEmptyState" class="prometheus-graphs">
+ <div
+ v-if="!showEmptyState"
+ class="prometheus-graphs"
+ >
<graph-group
v-for="(groupData, index) in store.groups"
:key="index"
diff --git a/app/assets/javascripts/monitoring/components/empty_state.vue b/app/assets/javascripts/monitoring/components/empty_state.vue
index a18164482a2..87d1975d5ad 100644
--- a/app/assets/javascripts/monitoring/components/empty_state.vue
+++ b/app/assets/javascripts/monitoring/components/empty_state.vue
@@ -33,13 +33,15 @@
gettingStarted: {
svgUrl: this.emptyGettingStartedSvgPath,
title: 'Get started with performance monitoring',
- description: 'Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments.',
+ description: `Stay updated about the performance and health
+of your environment by configuring Prometheus to monitor your deployments.`,
buttonText: 'Configure Prometheus',
},
loading: {
svgUrl: this.emptyLoadingSvgPath,
title: 'Waiting for performance data',
- description: 'Creating graphs uses the data from the Prometheus server. If this takes a long time, ensure that data is available.',
+ description: `Creating graphs uses the data from the Prometheus server.
+If this takes a long time, ensure that data is available.`,
buttonText: 'View documentation',
},
unableToConnect: {
@@ -74,20 +76,26 @@
<template>
<div class="prometheus-state">
<div class="state-svg svg-content">
- <img :src="currentState.svgUrl"/>
+ <img :src="currentState.svgUrl" />
</div>
<h4 class="state-title">
- {{currentState.title}}
+ {{ currentState.title }}
</h4>
<p class="state-description">
- {{currentState.description}}
- <a v-if="showButtonDescription" :href="settingsPath">
+ {{ currentState.description }}
+ <a
+ v-if="showButtonDescription"
+ :href="settingsPath"
+ >
Prometheus server
</a>
</p>
<div class="state-button">
- <a class="btn btn-success" :href="buttonPath">
- {{currentState.buttonText}}
+ <a
+ class="btn btn-success"
+ :href="buttonPath"
+ >
+ {{ currentState.buttonText }}
</a>
</div>
</div>
diff --git a/app/assets/javascripts/monitoring/components/graph.vue b/app/assets/javascripts/monitoring/components/graph.vue
index a50b80c23d0..ea5c24efaf9 100644
--- a/app/assets/javascripts/monitoring/components/graph.vue
+++ b/app/assets/javascripts/monitoring/components/graph.vue
@@ -17,6 +17,15 @@
const d3 = { scaleLinear, scaleTime, axisLeft, axisBottom, max, extent, select };
export default {
+ components: {
+ GraphLegend,
+ GraphFlag,
+ GraphDeployment,
+ GraphPath,
+ },
+
+ mixins: [MonitoringMixin],
+
props: {
graphData: {
type: Object,
@@ -45,8 +54,6 @@
},
},
- mixins: [MonitoringMixin],
-
data() {
return {
baseGraphHeight: 450,
@@ -74,13 +81,6 @@
};
},
- components: {
- GraphLegend,
- GraphFlag,
- GraphDeployment,
- GraphPath,
- },
-
computed: {
outerViewBox() {
return `0 0 ${this.baseGraphWidth} ${this.baseGraphHeight}`;
@@ -105,6 +105,26 @@
},
},
+ watch: {
+ updateAspectRatio() {
+ if (this.updateAspectRatio) {
+ this.graphHeight = 450;
+ this.graphWidth = 600;
+ this.measurements = measurements.large;
+ this.draw();
+ eventHub.$emit('toggleAspectRatio');
+ }
+ },
+
+ hoverData() {
+ this.positionFlag();
+ },
+ },
+
+ mounted() {
+ this.draw();
+ },
+
methods: {
draw() {
const breakpointSize = bp.getBreakpointSize();
@@ -197,51 +217,34 @@
}); // This will select all of the ticks once they're rendered
},
},
-
- watch: {
- updateAspectRatio() {
- if (this.updateAspectRatio) {
- this.graphHeight = 450;
- this.graphWidth = 600;
- this.measurements = measurements.large;
- this.draw();
- eventHub.$emit('toggleAspectRatio');
- }
- },
-
- hoverData() {
- this.positionFlag();
- },
- },
-
- mounted() {
- this.draw();
- },
};
</script>
<template>
- <div
+ <div
class="prometheus-graph"
@mouseover="showFlagContent = true"
- @mouseleave="showFlagContent = false">
+ @mouseleave="showFlagContent = false"
+ >
<h5 class="text-center graph-title">
- {{graphData.title}}
+ {{ graphData.title }}
</h5>
<div
class="prometheus-svg-container"
- :style="paddingBottomRootSvg">
+ :style="paddingBottomRootSvg"
+ >
<svg
:viewBox="outerViewBox"
- ref="baseSvg">
+ ref="baseSvg"
+ >
<g
class="x-axis"
- :transform="axisTransform">
- </g>
+ :transform="axisTransform"
+ />
<g
class="y-axis"
- transform="translate(70, 20)">
- </g>
+ transform="translate(70, 20)"
+ />
<graph-legend
:graph-width="graphWidth"
:graph-height="graphHeight"
@@ -256,29 +259,30 @@
<svg
class="graph-data"
:viewBox="innerViewBox"
- ref="graphData">
- <graph-path
- v-for="(path, index) in timeSeries"
- :key="index"
- :generated-line-path="path.linePath"
- :generated-area-path="path.areaPath"
- :line-style="path.lineStyle"
- :line-color="path.lineColor"
- :area-color="path.areaColor"
- />
- <graph-deployment
- :deployment-data="reducedDeploymentData"
- :graph-height="graphHeight"
- :graph-height-offset="graphHeightOffset"
- />
- <rect
- class="prometheus-graph-overlay"
- :width="(graphWidth - 70)"
- :height="(graphHeight - 100)"
- transform="translate(-5, 20)"
- ref="graphOverlay"
- @mousemove="handleMouseOverGraph($event)">
- </rect>
+ ref="graphData"
+ >
+ <graph-path
+ v-for="(path, index) in timeSeries"
+ :key="index"
+ :generated-line-path="path.linePath"
+ :generated-area-path="path.areaPath"
+ :line-style="path.lineStyle"
+ :line-color="path.lineColor"
+ :area-color="path.areaColor"
+ />
+ <graph-deployment
+ :deployment-data="reducedDeploymentData"
+ :graph-height="graphHeight"
+ :graph-height-offset="graphHeightOffset"
+ />
+ <rect
+ class="prometheus-graph-overlay"
+ :width="(graphWidth - 70)"
+ :height="(graphHeight - 100)"
+ transform="translate(-5, 20)"
+ ref="graphOverlay"
+ @mousemove="handleMouseOverGraph($event)"
+ />
</svg>
</svg>
<graph-flag
diff --git a/app/assets/javascripts/monitoring/components/graph/deployment.vue b/app/assets/javascripts/monitoring/components/graph/deployment.vue
index 8d6393d4ce5..98c25307b74 100644
--- a/app/assets/javascripts/monitoring/components/graph/deployment.vue
+++ b/app/assets/javascripts/monitoring/components/graph/deployment.vue
@@ -39,33 +39,35 @@
y="0"
:height="calculatedHeight"
width="3"
- fill="url(#shadow-gradient)">
- </rect>
+ fill="url(#shadow-gradient)"
+ />
<line
class="deployment-line"
x1="0"
y1="0"
x2="0"
:y2="calculatedHeight"
- stroke="#000">
- </line>
+ stroke="#000"
+ />
</g>
<svg
height="0"
- width="0">
+ width="0"
+ >
<defs>
<linearGradient
- id="shadow-gradient">
+ id="shadow-gradient"
+ >
<stop
offset="0%"
stop-color="#000"
- stop-opacity="0.4">
- </stop>
+ stop-opacity="0.4"
+ />
<stop
offset="100%"
stop-color="#000"
- stop-opacity="0">
- </stop>
+ stop-opacity="0"
+ />
</linearGradient>
</defs>
</svg>
diff --git a/app/assets/javascripts/monitoring/components/graph/flag.vue b/app/assets/javascripts/monitoring/components/graph/flag.vue
index 62ebc3f419c..07aa6a3e5de 100644
--- a/app/assets/javascripts/monitoring/components/graph/flag.vue
+++ b/app/assets/javascripts/monitoring/components/graph/flag.vue
@@ -1,9 +1,12 @@
<script>
import { dateFormat, timeFormat } from '../../utils/date_time_formatters';
import { formatRelevantDigits } from '../../../lib/utils/number_utils';
- import Icon from '../../../vue_shared/components/icon.vue';
+ import icon from '../../../vue_shared/components/icon.vue';
export default {
+ components: {
+ icon,
+ },
props: {
currentXCoordinate: {
type: Number,
@@ -52,10 +55,6 @@
},
},
- components: {
- Icon,
- },
-
computed: {
formatTime() {
return this.deploymentFlagData ?
@@ -137,33 +136,34 @@
>
<div class="arrow"></div>
<div class="popover-title">
- <h5 v-if="this.deploymentFlagData">
+ <h5 v-if="deploymentFlagData">
Deployed
</h5>
- {{formatDate}} at
- <strong>{{formatTime}}</strong>
+ {{ formatDate }} at
+ <strong>{{ formatTime }}</strong>
</div>
<div
- v-if="this.deploymentFlagData"
+ v-if="deploymentFlagData"
class="popover-content deploy-meta-content"
>
<div>
<icon
name="commit"
- :size="12">
- </icon>
+ :size="12"
+ />
<a :href="deploymentFlagData.commitUrl">
- {{deploymentFlagData.sha.slice(0, 8)}}
+ {{ deploymentFlagData.sha.slice(0, 8) }}
</a>
</div>
<div
- v-if="deploymentFlagData.tag">
+ v-if="deploymentFlagData.tag"
+ >
<icon
name="label"
- :size="12">
- </icon>
+ :size="12"
+ />
<a :href="deploymentFlagData.tagUrl">
- {{deploymentFlagData.ref}}
+ {{ deploymentFlagData.ref }}
</a>
</div>
</div>
@@ -174,7 +174,10 @@
:key="index"
>
<td>
- <svg width="15" height="6">
+ <svg
+ width="15"
+ height="6"
+ >
<line
:stroke="series.lineColor"
:stroke-dasharray="strokeDashArray(series.lineStyle)"
@@ -182,13 +185,13 @@
x1="0"
x2="15"
y1="2"
- y2="2">
- </line>
+ y2="2"
+ />
</svg>
</td>
- <td>{{seriesMetricLabel(index, series)}}</td>
+ <td>{{ seriesMetricLabel(index, series) }}</td>
<td>
- <strong>{{seriesMetricValue(series)}}</strong>
+ <strong>{{ seriesMetricValue(series) }}</strong>
</td>
</tr>
</table>
diff --git a/app/assets/javascripts/monitoring/components/graph/legend.vue b/app/assets/javascripts/monitoring/components/graph/legend.vue
index 440b1b12631..c6e8d726ffc 100644
--- a/app/assets/javascripts/monitoring/components/graph/legend.vue
+++ b/app/assets/javascripts/monitoring/components/graph/legend.vue
@@ -73,6 +73,21 @@
},
},
+ mounted() {
+ this.$nextTick(() => {
+ const bbox = this.$refs.ylabel.getBBox();
+ this.metricUsageXPosition = 0;
+ this.seriesXPosition = 0;
+ if (this.$refs.legendTitleSvg != null) {
+ this.seriesXPosition = this.$refs.legendTitleSvg[0].getBBox().width;
+ }
+ if (this.$refs.seriesTitleSvg != null) {
+ this.metricUsageXPosition = this.$refs.seriesTitleSvg[0].getBBox().width;
+ }
+ this.yLabelWidth = bbox.width + 10; // Added some padding
+ this.yLabelHeight = bbox.height + 5;
+ });
+ },
methods: {
translateLegendGroup(index) {
return `translate(0, ${12 * (index)})`;
@@ -100,26 +115,10 @@
return null;
},
},
- mounted() {
- this.$nextTick(() => {
- const bbox = this.$refs.ylabel.getBBox();
- this.metricUsageXPosition = 0;
- this.seriesXPosition = 0;
- if (this.$refs.legendTitleSvg != null) {
- this.seriesXPosition = this.$refs.legendTitleSvg[0].getBBox().width;
- }
- if (this.$refs.seriesTitleSvg != null) {
- this.metricUsageXPosition = this.$refs.seriesTitleSvg[0].getBBox().width;
- }
- this.yLabelWidth = bbox.width + 10; // Added some padding
- this.yLabelHeight = bbox.height + 5;
- });
- },
};
</script>
<template>
- <g
- class="axis-label-container">
+ <g class="axis-label-container">
<line
class="label-x-axis-line"
stroke="#000000"
@@ -127,8 +126,8 @@
x1="10"
:y1="yPosition"
:x2="graphWidth + 20"
- :y2="yPosition">
- </line>
+ :y2="yPosition"
+ />
<line
class="label-y-axis-line"
stroke="#000000"
@@ -136,39 +135,43 @@
x1="10"
y1="0"
:x2="10"
- :y2="yPosition">
- </line>
+ :y2="yPosition"
+ />
<rect
class="rect-axis-text"
:transform="rectTransform"
:width="yLabelWidth"
- :height="yLabelHeight">
- </rect>
+ :height="yLabelHeight"
+ />
<text
class="label-axis-text y-label-text"
text-anchor="middle"
:transform="textTransform"
- ref="ylabel">
- {{yAxisLabel}}
+ ref="ylabel"
+ >
+ {{ yAxisLabel }}
</text>
<rect
class="rect-axis-text"
:x="xPosition + 60"
:y="graphHeight - 80"
width="35"
- height="50">
- </rect>
+ height="50"
+ />
<text
class="label-axis-text x-label-text"
:x="xPosition + 60"
:y="yPosition"
- dy=".35em">
+ dy=".35em"
+ >
Time
</text>
- <g class="legend-group"
+ <g
+ class="legend-group"
v-for="(series, index) in timeSeries"
:key="index"
- :transform="translateLegendGroup(index)">
+ :transform="translateLegendGroup(index)"
+ >
<line
:stroke="series.lineColor"
:stroke-width="measurements.legends.height"
@@ -176,23 +179,25 @@
:x1="measurements.legends.offsetX"
:x2="measurements.legends.offsetX + measurements.legends.width"
:y1="graphHeight - measurements.legends.offsetY"
- :y2="graphHeight - measurements.legends.offsetY">
- </line>
+ :y2="graphHeight - measurements.legends.offsetY"
+ />
<text
v-if="timeSeries.length > 1"
class="legend-metric-title"
ref="legendTitleSvg"
x="38"
- :y="graphHeight - 30">
- {{createSeriesString(index, series)}}
+ :y="graphHeight - 30"
+ >
+ {{ createSeriesString(index, series) }}
</text>
<text
v-else
class="legend-metric-title"
ref="legendTitleSvg"
x="38"
- :y="graphHeight - 30">
- {{legendTitle}} {{formatMetricUsage(series)}}
+ :y="graphHeight - 30"
+ >
+ {{ legendTitle }} {{ formatMetricUsage(series) }}
</text>
</g>
</g>
diff --git a/app/assets/javascripts/monitoring/components/graph/path.vue b/app/assets/javascripts/monitoring/components/graph/path.vue
index 5e6d409033a..c9721c4cb01 100644
--- a/app/assets/javascripts/monitoring/components/graph/path.vue
+++ b/app/assets/javascripts/monitoring/components/graph/path.vue
@@ -12,6 +12,7 @@
lineStyle: {
type: String,
required: false,
+ default: '',
},
lineColor: {
type: String,
@@ -37,8 +38,8 @@
class="metric-area"
:d="generatedAreaPath"
:fill="areaColor"
- transform="translate(-5, 20)">
- </path>
+ transform="translate(-5, 20)"
+ />
<path
class="metric-line"
:d="generatedLinePath"
@@ -46,7 +47,7 @@
fill="none"
stroke-width="1"
:stroke-dasharray="strokeDashArray"
- transform="translate(-5, 20)">
- </path>
+ transform="translate(-5, 20)"
+ />
</g>
</template>
diff --git a/app/assets/javascripts/monitoring/components/graph_group.vue b/app/assets/javascripts/monitoring/components/graph_group.vue
index 958f537d31b..079351a69af 100644
--- a/app/assets/javascripts/monitoring/components/graph_group.vue
+++ b/app/assets/javascripts/monitoring/components/graph_group.vue
@@ -1,21 +1,21 @@
<script>
-export default {
- props: {
- name: {
- type: String,
- required: true,
+ export default {
+ props: {
+ name: {
+ type: String,
+ required: true,
+ },
},
- },
-};
+ };
</script>
<template>
<div class="panel panel-default prometheus-panel">
<div class="panel-heading">
- <h4>{{name}}</h4>
+ <h4>{{ name }}</h4>
</div>
<div class="panel-body prometheus-graph-group">
- <slot />
+ <slot></slot>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/notebook/cells/markdown.vue b/app/assets/javascripts/notebook/cells/markdown.vue
index 82c51a1068c..d0ec70f1fcf 100644
--- a/app/assets/javascripts/notebook/cells/markdown.vue
+++ b/app/assets/javascripts/notebook/cells/markdown.vue
@@ -91,18 +91,21 @@
<template>
<div class="cell text-cell">
<prompt />
- <div class="markdown" v-html="markdown"></div>
+ <div
+ class="markdown"
+ v-html="markdown">
+ </div>
</div>
</template>
<style>
-.markdown .katex {
- display: block;
- text-align: center;
-}
+ .markdown .katex {
+ display: block;
+ text-align: center;
+ }
-.markdown .inline-katex .katex {
- display: inline;
- text-align: initial;
-}
+ .markdown .inline-katex .katex {
+ display: inline;
+ text-align: initial;
+ }
</style>
diff --git a/app/assets/javascripts/notebook/cells/output/html.vue b/app/assets/javascripts/notebook/cells/output/html.vue
index 2110a9de7ed..ebba5954de9 100644
--- a/app/assets/javascripts/notebook/cells/output/html.vue
+++ b/app/assets/javascripts/notebook/cells/output/html.vue
@@ -1,17 +1,17 @@
<script>
-import Prompt from '../prompt.vue';
+ import Prompt from '../prompt.vue';
-export default {
- props: {
- rawCode: {
- type: String,
- required: true,
+ export default {
+ components: {
+ prompt: Prompt,
},
- },
- components: {
- prompt: Prompt,
- },
-};
+ props: {
+ rawCode: {
+ type: String,
+ required: true,
+ },
+ },
+ };
</script>
<template>
diff --git a/app/assets/javascripts/notebook/cells/output/image.vue b/app/assets/javascripts/notebook/cells/output/image.vue
index fbb39ea6e2d..67d6c5ad12b 100644
--- a/app/assets/javascripts/notebook/cells/output/image.vue
+++ b/app/assets/javascripts/notebook/cells/output/image.vue
@@ -1,27 +1,26 @@
<script>
-import Prompt from '../prompt.vue';
+ import Prompt from '../prompt.vue';
-export default {
- props: {
- outputType: {
- type: String,
- required: true,
+ export default {
+ components: {
+ prompt: Prompt,
},
- rawCode: {
- type: String,
- required: true,
+ props: {
+ outputType: {
+ type: String,
+ required: true,
+ },
+ rawCode: {
+ type: String,
+ required: true,
+ },
},
- },
- components: {
- prompt: Prompt,
- },
-};
+ };
</script>
<template>
<div class="output">
<prompt />
- <img
- :src="'data:' + outputType + ';base64,' + rawCode" />
+ <img :src="'data:' + outputType + ';base64,' + rawCode" />
</div>
</template>
diff --git a/app/assets/javascripts/notebook/cells/output/index.vue b/app/assets/javascripts/notebook/cells/output/index.vue
index 05af0bf1e8e..91b2269a83a 100644
--- a/app/assets/javascripts/notebook/cells/output/index.vue
+++ b/app/assets/javascripts/notebook/cells/output/index.vue
@@ -1,83 +1,87 @@
<script>
-import CodeCell from '../code/index.vue';
-import Html from './html.vue';
-import Image from './image.vue';
+ import CodeCell from '../code/index.vue';
+ import Html from './html.vue';
+ import Image from './image.vue';
-export default {
- props: {
- codeCssClass: {
- type: String,
- required: false,
- default: '',
+ export default {
+ components: {
+ 'code-cell': CodeCell,
+ 'html-output': Html,
+ 'image-output': Image,
},
- count: {
- type: Number,
- required: false,
- default: 0,
+ props: {
+ codeCssClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ count: {
+ type: Number,
+ required: false,
+ default: 0,
+ },
+ output: {
+ type: Object,
+ requred: true,
+ default: () => ({}),
+ },
},
- output: {
- type: Object,
- requred: true,
- },
- },
- components: {
- 'code-cell': CodeCell,
- 'html-output': Html,
- 'image-output': Image,
- },
- data() {
- return {
- outputType: '',
- };
- },
- computed: {
- componentName() {
- if (this.output.text) {
- return 'code-cell';
- } else if (this.output.data['image/png']) {
- this.outputType = 'image/png';
-
- return 'image-output';
- } else if (this.output.data['text/html']) {
- this.outputType = 'text/html';
+ computed: {
+ componentName() {
+ if (this.output.text) {
+ return 'code-cell';
+ } else if (this.output.data['image/png']) {
+ return 'image-output';
+ } else if (this.output.data['text/html']) {
+ return 'html-output';
+ } else if (this.output.data['image/svg+xml']) {
+ return 'html-output';
+ }
- return 'html-output';
- } else if (this.output.data['image/svg+xml']) {
- this.outputType = 'image/svg+xml';
-
- return 'html-output';
- }
+ return 'code-cell';
+ },
+ rawCode() {
+ if (this.output.text) {
+ return this.output.text.join('');
+ }
- this.outputType = 'text/plain';
- return 'code-cell';
- },
- rawCode() {
- if (this.output.text) {
- return this.output.text.join('');
- }
+ return this.dataForType(this.outputType);
+ },
+ outputType() {
+ if (this.output.text) {
+ return '';
+ } else if (this.output.data['image/png']) {
+ return 'image/png';
+ } else if (this.output.data['text/html']) {
+ return 'text/html';
+ } else if (this.output.data['image/svg+xml']) {
+ return 'image/svg+xml';
+ }
- return this.dataForType(this.outputType);
+ return 'text/plain';
+ },
},
- },
- methods: {
- dataForType(type) {
- let data = this.output.data[type];
+ methods: {
+ dataForType(type) {
+ let data = this.output.data[type];
- if (typeof data === 'object') {
- data = data.join('');
- }
+ if (typeof data === 'object') {
+ data = data.join('');
+ }
- return data;
+ return data;
+ },
},
- },
-};
+ };
</script>
<template>
- <component :is="componentName"
+ <component
+ :is="componentName"
type="output"
- :outputType="outputType"
+ :output-type="outputType"
:count="count"
:raw-code="rawCode"
- :code-css-class="codeCssClass" />
+ :code-css-class="codeCssClass"
+ />
</template>
diff --git a/app/assets/javascripts/notebook/cells/prompt.vue b/app/assets/javascripts/notebook/cells/prompt.vue
index 039fb99293d..fe1fc37e1dc 100644
--- a/app/assets/javascripts/notebook/cells/prompt.vue
+++ b/app/assets/javascripts/notebook/cells/prompt.vue
@@ -4,10 +4,17 @@
type: {
type: String,
required: false,
+ default: '',
},
count: {
type: Number,
required: false,
+ default: 0,
+ },
+ },
+ computed: {
+ hasKeys() {
+ return this.type !== '' && this.count;
},
},
};
@@ -15,16 +22,16 @@
<template>
<div class="prompt">
- <span v-if="type && count">
+ <span v-if="hasKeys">
{{ type }} [{{ count }}]:
</span>
</div>
</template>
<style scoped>
-.prompt {
- padding: 0 10px;
- min-width: 7em;
- font-family: monospace;
-}
+ .prompt {
+ padding: 0 10px;
+ min-width: 7em;
+ font-family: monospace;
+ }
</style>
diff --git a/app/assets/javascripts/notebook/index.vue b/app/assets/javascripts/notebook/index.vue
index e88806431af..e2e3b08c77f 100644
--- a/app/assets/javascripts/notebook/index.vue
+++ b/app/assets/javascripts/notebook/index.vue
@@ -20,11 +20,6 @@
default: '',
},
},
- methods: {
- cellType(type) {
- return `${type}-cell`;
- },
- },
computed: {
cells() {
if (this.notebook.worksheets) {
@@ -45,6 +40,11 @@
return Object.keys(this.notebook).length;
},
},
+ methods: {
+ cellType(type) {
+ return `${type}-cell`;
+ },
+ },
};
</script>
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index e594377bc40..1f18c196137 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -15,7 +15,17 @@
import issuableStateMixin from '../mixins/issuable_state';
export default {
- name: 'commentForm',
+ name: 'CommentForm',
+ components: {
+ issueWarning,
+ noteSignedOutWidget,
+ discussionLockedWidget,
+ markdownField,
+ userAvatarLink,
+ },
+ mixins: [
+ issuableStateMixin,
+ ],
data() {
return {
note: '',
@@ -27,21 +37,6 @@
isSubmitButtonDisabled: true,
};
},
- components: {
- issueWarning,
- noteSignedOutWidget,
- discussionLockedWidget,
- markdownField,
- userAvatarLink,
- },
- watch: {
- note(newNote) {
- this.setIsSubmitButtonDisabled(newNote, this.isSubmitting);
- },
- isSubmitting(newValue) {
- this.setIsSubmitButtonDisabled(this.note, newValue);
- },
- },
computed: {
...mapGetters([
'getCurrentUserLastNote',
@@ -65,7 +60,9 @@
if (this.note.length) {
const actionText = this.isIssueOpen ? 'close' : 'reopen';
- return this.noteType === constants.COMMENT ? `Comment & ${actionText} issue` : `Start discussion & ${actionText} issue`;
+ return this.noteType === constants.COMMENT ?
+ `Comment & ${actionText} issue` :
+ `Start discussion & ${actionText} issue`;
}
return this.isIssueOpen ? 'Close issue' : 'Reopen issue';
@@ -97,6 +94,23 @@
return this.getNoteableData.create_note_path;
},
},
+ watch: {
+ note(newNote) {
+ this.setIsSubmitButtonDisabled(newNote, this.isSubmitting);
+ },
+ isSubmitting(newValue) {
+ this.setIsSubmitButtonDisabled(this.note, newValue);
+ },
+ },
+ mounted() {
+ // jQuery is needed here because it is a custom event being dispatched with jQuery.
+ $(document).on('issuable:change', (e, isClosed) => {
+ this.issueState = isClosed ? constants.CLOSED : constants.REOPENED;
+ });
+
+ this.initAutoSave();
+ this.initTaskList();
+ },
methods: {
...mapActions([
'saveNote',
@@ -159,7 +173,9 @@
.catch(() => {
this.isSubmitting = false;
this.discard(false);
- const msg = 'Your comment could not be submitted! Please check your network connection and try again.';
+ const msg =
+ `Your comment could not be submitted!
+Please check your network connection and try again.`;
Flash(msg, 'alert', this.$el);
this.note = noteData.data.note.note; // Restore textarea content.
this.removePlaceholderNotes();
@@ -207,7 +223,11 @@
},
initAutoSave() {
if (this.isLoggedIn) {
- this.autosave = new Autosave($(this.$refs.textarea), ['Note', 'Issue', this.getNoteableData.id], 'issue');
+ this.autosave = new Autosave(
+ $(this.$refs.textarea),
+ ['Note', 'Issue', this.getNoteableData.id],
+ 'issue',
+ );
}
},
initTaskList() {
@@ -223,18 +243,6 @@
});
},
},
- mixins: [
- issuableStateMixin,
- ],
- mounted() {
- // jQuery is needed here because it is a custom event being dispatched with jQuery.
- $(document).on('issuable:change', (e, isClosed) => {
- this.issueState = isClosed ? constants.CLOSED : constants.REOPENED;
- });
-
- this.initAutoSave();
- this.initTaskList();
- },
};
</script>
@@ -258,7 +266,7 @@
:img-src="author.avatar_url"
:img-alt="author.name"
:img-size="40"
- />
+ />
</div>
<div class="timeline-content timeline-content-form">
<form
@@ -283,7 +291,8 @@
<textarea
id="note-body"
name="note[note]"
- class="note-textarea js-vue-comment-form js-gfm-input js-autosize markdown-area js-vue-textarea"
+ class="note-textarea js-vue-comment-form
+js-gfm-input js-autosize markdown-area js-vue-textarea"
data-supports-quick-actions="true"
aria-label="Description"
v-model="note"
@@ -296,13 +305,15 @@
</textarea>
</markdown-field>
<div class="note-form-actions">
- <div class="pull-left btn-group append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown">
+ <div
+ class="pull-left btn-group
+append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown">
<button
@click.prevent="handleSave()"
:disabled="isSubmitButtonDisabled"
class="btn btn-create comment-btn js-comment-button js-comment-submit-button"
type="submit">
- {{commentButtonTitle}}
+ {{ commentButtonTitle }}
</button>
<button
:disabled="isSubmitButtonDisabled"
@@ -344,7 +355,7 @@
<i
aria-hidden="true"
class="fa fa-check icon">
- </i>
+ </i>
<div class="description">
<strong>Start discussion</strong>
<p>
@@ -362,7 +373,7 @@
:class="actionButtonClassNames"
:disabled="isSubmitting"
class="btn btn-comment btn-comment-and-close js-action-button">
- {{issueActionButtonTitle}}
+ {{ issueActionButtonTitle }}
</button>
<button
type="button"
diff --git a/app/assets/javascripts/notes/components/discussion_locked_widget.vue b/app/assets/javascripts/notes/components/discussion_locked_widget.vue
index e6f7ee56ff3..fc0722042cc 100644
--- a/app/assets/javascripts/notes/components/discussion_locked_widget.vue
+++ b/app/assets/javascripts/notes/components/discussion_locked_widget.vue
@@ -3,12 +3,12 @@
import Issuable from '~/vue_shared/mixins/issuable';
export default {
- mixins: [
- Issuable,
- ],
components: {
Icon,
},
+ mixins: [
+ Issuable,
+ ],
};
</script>
@@ -18,9 +18,11 @@
<icon
name="lock"
:size="16"
- class="icon">
- </icon>
- <span>This {{ issuableDisplayName }} is locked. Only <b>project members</b> can comment.</span>
- </span>
+ class="icon"
+ />
+ <span>
+ This {{ issuableDisplayName }} is locked. Only <b>project members</b> can comment.
+ </span>
+ </span>
</div>
</template>
diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue
index 7fb45ed4d4b..46ffb60aa60 100644
--- a/app/assets/javascripts/notes/components/note_actions.vue
+++ b/app/assets/javascripts/notes/components/note_actions.vue
@@ -9,7 +9,13 @@
import tooltip from '~/vue_shared/directives/tooltip';
export default {
- name: 'noteActions',
+ name: 'NoteActions',
+ directives: {
+ tooltip,
+ },
+ components: {
+ loadingIcon,
+ },
props: {
authorId: {
type: Number,
@@ -41,12 +47,6 @@
required: true,
},
},
- directives: {
- tooltip,
- },
- components: {
- loadingIcon,
- },
computed: {
...mapGetters([
'getUserDataByProp',
@@ -64,6 +64,13 @@
return this.getUserDataByProp('id');
},
},
+ created() {
+ this.emojiSmiling = emojiSmiling;
+ this.emojiSmile = emojiSmile;
+ this.emojiSmiley = emojiSmiley;
+ this.editSvg = editSvg;
+ this.ellipsisSvg = ellipsisSvg;
+ },
methods: {
onEdit() {
this.$emit('handleEdit');
@@ -72,13 +79,6 @@
this.$emit('handleDelete');
},
},
- created() {
- this.emojiSmiling = emojiSmiling;
- this.emojiSmile = emojiSmile;
- this.emojiSmiley = emojiSmiley;
- this.editSvg = editSvg;
- this.ellipsisSvg = ellipsisSvg;
- },
};
</script>
@@ -86,7 +86,9 @@
<div class="note-actions">
<span
v-if="accessLevel"
- class="note-role user-access-role">{{accessLevel}}</span>
+ class="note-role user-access-role">
+ {{ accessLevel }}
+ </span>
<div
v-if="canAddAwardEmoji"
class="note-actions-item">
@@ -98,20 +100,21 @@
data-placement="bottom"
data-container="body"
href="#"
- title="Add reaction">
- <loading-icon :inline="true" />
- <span
- v-html="emojiSmiling"
- class="link-highlight award-control-icon-neutral">
- </span>
- <span
- v-html="emojiSmiley"
- class="link-highlight award-control-icon-positive">
- </span>
- <span
- v-html="emojiSmile"
- class="link-highlight award-control-icon-super-positive">
- </span>
+ title="Add reaction"
+ >
+ <loading-icon :inline="true" />
+ <span
+ v-html="emojiSmiling"
+ class="link-highlight award-control-icon-neutral">
+ </span>
+ <span
+ v-html="emojiSmiley"
+ class="link-highlight award-control-icon-positive">
+ </span>
+ <span
+ v-html="emojiSmile"
+ class="link-highlight award-control-icon-super-positive">
+ </span>
</a>
</div>
<div
@@ -125,9 +128,10 @@
class="note-action-button js-note-edit btn btn-transparent"
data-container="body"
data-placement="bottom">
- <span
- v-html="editSvg"
- class="link-highlight"></span>
+ <span
+ v-html="editSvg"
+ class="link-highlight">
+ </span>
</button>
</div>
<div
@@ -141,9 +145,10 @@
data-toggle="dropdown"
data-container="body"
data-placement="bottom">
- <span
- class="icon"
- v-html="ellipsisSvg"></span>
+ <span
+ class="icon"
+ v-html="ellipsisSvg">
+ </span>
</button>
<ul class="dropdown-menu more-actions-dropdown dropdown-open-left">
<li v-if="canReportAsAbuse">
diff --git a/app/assets/javascripts/notes/components/note_attachment.vue b/app/assets/javascripts/notes/components/note_attachment.vue
index cd9571a4002..618b807b9cc 100644
--- a/app/assets/javascripts/notes/components/note_attachment.vue
+++ b/app/assets/javascripts/notes/components/note_attachment.vue
@@ -1,6 +1,6 @@
<script>
export default {
- name: 'noteAttachment',
+ name: 'NoteAttachment',
props: {
attachment: {
type: Object,
@@ -19,7 +19,8 @@
rel="noopener noreferrer">
<img
:src="attachment.url"
- class="note-image-attach" />
+ class="note-image-attach"
+ />
</a>
<div class="attachment">
<a
@@ -29,8 +30,9 @@
rel="noopener noreferrer">
<i
class="fa fa-paperclip"
- aria-hidden="true"></i>
- {{attachment.filename}}
+ aria-hidden="true">
+ </i>
+ {{ attachment.filename }}
</a>
</div>
</div>
diff --git a/app/assets/javascripts/notes/components/note_awards_list.vue b/app/assets/javascripts/notes/components/note_awards_list.vue
index c3a340139e7..caa9701e03f 100644
--- a/app/assets/javascripts/notes/components/note_awards_list.vue
+++ b/app/assets/javascripts/notes/components/note_awards_list.vue
@@ -8,6 +8,9 @@
import tooltip from '../../vue_shared/directives/tooltip';
export default {
+ directives: {
+ tooltip,
+ },
props: {
awards: {
type: Array,
@@ -26,9 +29,6 @@
required: true,
},
},
- directives: {
- tooltip,
- },
computed: {
...mapGetters([
'getUserData',
@@ -73,6 +73,11 @@
return this.getUserData.id;
},
},
+ created() {
+ this.emojiSmiling = emojiSmiling;
+ this.emojiSmile = emojiSmile;
+ this.emojiSmiley = emojiSmiley;
+ },
methods: {
...mapActions([
'toggleAwardRequest',
@@ -168,11 +173,6 @@
.catch(() => Flash('Something went wrong on our end.'));
},
},
- created() {
- this.emojiSmiling = emojiSmiling;
- this.emojiSmile = emojiSmile;
- this.emojiSmiley = emojiSmiley;
- },
};
</script>
@@ -191,7 +191,7 @@
type="button">
<span v-html="getAwardHTML(awardName)"></span>
<span class="award-control-text js-counter">
- {{awardList.length}}
+ {{ awardList.length }}
</span>
</button>
<div
diff --git a/app/assets/javascripts/notes/components/note_body.vue b/app/assets/javascripts/notes/components/note_body.vue
index ac4e1ffe53a..2d7cd30115d 100644
--- a/app/assets/javascripts/notes/components/note_body.vue
+++ b/app/assets/javascripts/notes/components/note_body.vue
@@ -7,6 +7,15 @@
import autosave from '../mixins/autosave';
export default {
+ components: {
+ noteEditedText,
+ noteAwardsList,
+ noteAttachment,
+ noteForm,
+ },
+ mixins: [
+ autosave,
+ ],
props: {
note: {
type: Object,
@@ -22,40 +31,11 @@
default: false,
},
},
- mixins: [
- autosave,
- ],
- components: {
- noteEditedText,
- noteAwardsList,
- noteAttachment,
- noteForm,
- },
computed: {
noteBody() {
return this.note.note;
},
},
- methods: {
- renderGFM() {
- $(this.$refs['note-body']).renderGFM();
- },
- initTaskList() {
- if (this.canEdit) {
- this.taskList = new TaskList({
- dataType: 'note',
- fieldName: 'note',
- selector: '.notes',
- });
- }
- },
- handleFormUpdate(note, parentElement, callback) {
- this.$emit('handleFormUpdate', note, parentElement, callback);
- },
- formCancelHandler(shouldConfirm, isDirty) {
- this.$emit('cancelFormEdition', shouldConfirm, isDirty);
- },
- },
mounted() {
this.renderGFM();
this.initTaskList();
@@ -76,6 +56,26 @@
}
}
},
+ methods: {
+ renderGFM() {
+ $(this.$refs['note-body']).renderGFM();
+ },
+ initTaskList() {
+ if (this.canEdit) {
+ this.taskList = new TaskList({
+ dataType: 'note',
+ fieldName: 'note',
+ selector: '.notes',
+ });
+ }
+ },
+ handleFormUpdate(note, parentElement, callback) {
+ this.$emit('handleFormUpdate', note, parentElement, callback);
+ },
+ formCancelHandler(shouldConfirm, isDirty) {
+ this.$emit('cancelFormEdition', shouldConfirm, isDirty);
+ },
+ },
};
</script>
@@ -95,7 +95,7 @@
:is-editing="isEditing"
:note-body="noteBody"
:note-id="note.id"
- />
+ />
<textarea
v-if="canEdit"
v-model="note.note"
@@ -106,17 +106,17 @@
:edited-at="note.last_edited_at"
:edited-by="note.last_edited_by"
action-text="Edited"
- />
+ />
<note-awards-list
v-if="note.award_emoji.length"
:note-id="note.id"
:note-author-id="note.author.id"
:awards="note.award_emoji"
:toggle-award-path="note.toggle_award_path"
- />
+ />
<note-attachment
v-if="note.attachment"
:attachment="note.attachment"
- />
+ />
</div>
</template>
diff --git a/app/assets/javascripts/notes/components/note_edited_text.vue b/app/assets/javascripts/notes/components/note_edited_text.vue
index 49e09f0ecc5..ae2e52554d2 100644
--- a/app/assets/javascripts/notes/components/note_edited_text.vue
+++ b/app/assets/javascripts/notes/components/note_edited_text.vue
@@ -2,7 +2,10 @@
import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
export default {
- name: 'editedNoteText',
+ name: 'EditedNoteText',
+ components: {
+ timeAgoTooltip,
+ },
props: {
actionText: {
type: String,
@@ -15,6 +18,7 @@
editedBy: {
type: Object,
required: false,
+ default: () => ({}),
},
className: {
type: String,
@@ -22,25 +26,22 @@
default: 'edited-text',
},
},
- components: {
- timeAgoTooltip,
- },
};
</script>
<template>
<div :class="className">
- {{actionText}}
+ {{ actionText }}
<time-ago-tooltip
:time="editedAt"
tooltip-placement="bottom"
- />
+ />
<template v-if="editedBy">
by
<a
:href="editedBy.path"
class="js-vue-author author_link">
- {{editedBy.name}}
+ {{ editedBy.name }}
</a>
</template>
</div>
diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue
index 4d527cb6643..aeda3497715 100644
--- a/app/assets/javascripts/notes/components/note_form.vue
+++ b/app/assets/javascripts/notes/components/note_form.vue
@@ -6,7 +6,14 @@
import issuableStateMixin from '../mixins/issuable_state';
export default {
- name: 'issueNoteForm',
+ name: 'IssueNoteForm',
+ components: {
+ issueWarning,
+ markdownField,
+ },
+ mixins: [
+ issuableStateMixin,
+ ],
props: {
noteBody: {
type: String,
@@ -16,6 +23,7 @@
noteId: {
type: Number,
required: false,
+ default: 0,
},
saveButtonTitle: {
type: String,
@@ -39,10 +47,6 @@
isSubmitting: false,
};
},
- components: {
- issueWarning,
- markdownField,
- },
computed: {
...mapGetters([
'getDiscussionLastNote',
@@ -70,6 +74,18 @@
return !this.note.length || this.isSubmitting;
},
},
+ watch: {
+ noteBody() {
+ if (this.note === this.noteBody) {
+ this.note = this.noteBody;
+ } else {
+ this.conflictWhileEditing = true;
+ }
+ },
+ },
+ mounted() {
+ this.$refs.textarea.focus();
+ },
methods: {
handleUpdate() {
this.isSubmitting = true;
@@ -94,26 +110,13 @@
this.$emit('cancelFormEdition', shouldConfirm, this.noteBody !== this.note);
},
},
- mixins: [
- issuableStateMixin,
- ],
- mounted() {
- this.$refs.textarea.focus();
- },
- watch: {
- noteBody() {
- if (this.note === this.noteBody) {
- this.note = this.noteBody;
- } else {
- this.conflictWhileEditing = true;
- }
- },
- },
};
</script>
<template>
- <div ref="editNoteForm" class="note-edit-form current-note-edit-form">
+ <div
+ ref="editNoteForm"
+ class="note-edit-form current-note-edit-form">
<div
v-if="conflictWhileEditing"
class="js-conflict-edit-warning alert alert-danger">
@@ -121,12 +124,13 @@
<a
:href="noteHash"
target="_blank"
- rel="noopener noreferrer">updated comment</a>
- to ensure information is not lost.
+ rel="noopener noreferrer">
+ updated comment
+ </a>
+ to ensure information is not lost.
</div>
<div class="flash-container timeline-content"></div>
- <form
- class="edit-note common-note-form js-quick-submit gfm-form">
+ <form class="edit-note common-note-form js-quick-submit gfm-form">
<issue-warning
v-if="hasWarning(getNoteableData)"
@@ -142,7 +146,8 @@
<textarea
id="note_note"
name="note[note]"
- class="note-textarea js-gfm-input js-autosize markdown-area js-vue-issue-note-form js-vue-textarea"
+ class="note-textarea js-gfm-input
+js-autosize markdown-area js-vue-issue-note-form js-vue-textarea"
:data-supports-quick-actions="!isEditing"
aria-label="Description"
v-model="note"
@@ -160,7 +165,7 @@
@click="handleUpdate()"
:disabled="isDisabled"
class="js-vue-issue-save btn btn-save">
- {{saveButtonTitle}}
+ {{ saveButtonTitle }}
</button>
<button
@click="cancelHandler()"
diff --git a/app/assets/javascripts/notes/components/note_header.vue b/app/assets/javascripts/notes/components/note_header.vue
index 63aa3d777d0..b28dda4904d 100644
--- a/app/assets/javascripts/notes/components/note_header.vue
+++ b/app/assets/javascripts/notes/components/note_header.vue
@@ -3,6 +3,9 @@
import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
export default {
+ components: {
+ timeAgoTooltip,
+ },
props: {
author: {
type: Object,
@@ -37,9 +40,6 @@
isExpanded: true,
};
},
- components: {
- timeAgoTooltip,
- },
computed: {
toggleChevronClass() {
return this.isExpanded ? 'fa-chevron-up' : 'fa-chevron-down';
@@ -67,16 +67,16 @@
<div class="note-header-info">
<a :href="author.path">
<span class="note-header-author-name">
- {{author.name}}
+ {{ author.name }}
</span>
<span class="note-headline-light">
- @{{author.username}}
+ @{{ author.username }}
</span>
</a>
<span class="note-headline-light">
<span class="note-headline-meta">
<template v-if="actionText">
- {{actionText}}
+ {{ actionText }}
</template>
<span
v-if="actionTextHtml"
@@ -90,12 +90,13 @@
<time-ago-tooltip
:time="createdAt"
tooltip-placement="bottom"
- />
+ />
</a>
<i
class="fa fa-spinner fa-spin editing-spinner"
aria-label="Comment is being updated"
- aria-hidden="true">
+ aria-hidden="true"
+ >
</i>
</span>
</span>
@@ -106,12 +107,12 @@
@click="handleToggle"
class="note-action-button discussion-toggle-button js-vue-toggle-button"
type="button">
- <i
- :class="toggleChevronClass"
- class="fa"
- aria-hidden="true">
- </i>
- Toggle discussion
+ <i
+ :class="toggleChevronClass"
+ class="fa"
+ aria-hidden="true">
+ </i>
+ Toggle discussion
</button>
</div>
</div>
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index 11e8f805635..98a06c5fc71 100644
--- a/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -13,17 +13,6 @@
import autosave from '../mixins/autosave';
export default {
- props: {
- note: {
- type: Object,
- required: true,
- },
- },
- data() {
- return {
- isReplying: false,
- };
- },
components: {
noteableNote,
userAvatarLink,
@@ -37,6 +26,17 @@
mixins: [
autosave,
],
+ props: {
+ note: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ isReplying: false,
+ };
+ },
computed: {
...mapGetters([
'getNoteableData',
@@ -72,6 +72,20 @@
return null;
},
},
+ mounted() {
+ if (this.isReplying) {
+ this.initAutoSave();
+ }
+ },
+ updated() {
+ if (this.isReplying) {
+ if (!this.autosave) {
+ this.initAutoSave();
+ } else {
+ this.setAutoSave();
+ }
+ }
+ },
methods: {
...mapActions([
'saveNote',
@@ -130,7 +144,8 @@
this.removePlaceholderNotes();
this.isReplying = true;
this.$nextTick(() => {
- const msg = 'Your comment could not be submitted! Please check your network connection and try again.';
+ const msg = `Your comment could not be submitted!
+Please check your network connection and try again.`;
Flash(msg, 'alert', this.$el);
this.$refs.noteForm.note = noteText;
callback(err);
@@ -138,20 +153,6 @@
});
},
},
- mounted() {
- if (this.isReplying) {
- this.initAutoSave();
- }
- },
- updated() {
- if (this.isReplying) {
- if (!this.autosave) {
- this.initAutoSave();
- } else {
- this.setAutoSave();
- }
- }
- },
};
</script>
@@ -164,7 +165,7 @@
:img-src="author.avatar_url"
:img-alt="author.name"
:img-size="40"
- />
+ />
</div>
<div class="timeline-content">
<div class="discussion">
@@ -184,42 +185,43 @@
:edited-by="lastUpdatedBy"
action-text="Last updated"
class-name="discussion-headline-light js-discussion-headline"
- />
- </div>
+ />
</div>
- <div
- v-if="note.expanded"
- class="discussion-body">
- <div class="panel panel-default">
- <div class="discussion-notes">
- <ul class="notes">
- <component
- v-for="note in note.notes"
- :is="componentName(note)"
- :note="componentData(note)"
- :key="note.id"
- />
- </ul>
- <div
- :class="{ 'is-replying': isReplying }"
- class="discussion-reply-holder">
- <button
- v-if="canReply && !isReplying"
- @click="showReplyForm"
- type="button"
- class="js-vue-discussion-reply btn btn-text-field"
- title="Add a reply">Reply...</button>
- <note-form
- v-if="isReplying"
- save-button-title="Comment"
- :discussion="note"
- :is-editing="false"
- @handleFormUpdate="saveReply"
- @cancelFormEdition="cancelReplyForm"
- ref="noteForm"
- />
- <note-signed-out-widget v-if="!canReply" />
- </div>
+ </div>
+ <div
+ v-if="note.expanded"
+ class="discussion-body">
+ <div class="panel panel-default">
+ <div class="discussion-notes">
+ <ul class="notes">
+ <component
+ v-for="note in note.notes"
+ :is="componentName(note)"
+ :note="componentData(note)"
+ :key="note.id"
+ />
+ </ul>
+ <div
+ :class="{ 'is-replying': isReplying }"
+ class="discussion-reply-holder">
+ <button
+ v-if="canReply && !isReplying"
+ @click="showReplyForm"
+ type="button"
+ class="js-vue-discussion-reply btn btn-text-field"
+ title="Add a reply">
+ Reply...
+ </button>
+ <note-form
+ v-if="isReplying"
+ save-button-title="Comment"
+ :discussion="note"
+ :is-editing="false"
+ @handleFormUpdate="saveReply"
+ @cancelFormEdition="cancelReplyForm"
+ ref="noteForm"
+ />
+ <note-signed-out-widget v-if="!canReply" />
</div>
</div>
</div>
diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue
index 9186d6ff64a..30e7ccc8229 100644
--- a/app/assets/javascripts/notes/components/noteable_note.vue
+++ b/app/assets/javascripts/notes/components/noteable_note.vue
@@ -9,6 +9,12 @@
import eventHub from '../event_hub';
export default {
+ components: {
+ userAvatarLink,
+ noteHeader,
+ noteActions,
+ noteBody,
+ },
props: {
note: {
type: Object,
@@ -22,12 +28,6 @@
isRequesting: false,
};
},
- components: {
- userAvatarLink,
- noteHeader,
- noteActions,
- noteBody,
- },
computed: {
...mapGetters([
'targetNoteHash',
@@ -51,6 +51,16 @@
return `note_${this.note.id}`;
},
},
+
+ created() {
+ eventHub.$on('enterEditMode', ({ noteId }) => {
+ if (noteId === this.note.id) {
+ this.isEditing = true;
+ this.scrollToNoteIfNeeded($(this.$el));
+ }
+ });
+ },
+
methods: {
...mapActions([
'deleteNote',
@@ -126,14 +136,6 @@
this.$refs.noteBody.$refs.noteForm.note = noteText;
},
},
- created() {
- eventHub.$on('enterEditMode', ({ noteId }) => {
- if (noteId === this.note.id) {
- this.isEditing = true;
- this.scrollToNoteIfNeeded($(this.$el));
- }
- });
- },
};
</script>
@@ -150,7 +152,7 @@
:img-src="author.avatar_url"
:img-alt="author.name"
:img-size="40"
- />
+ />
</div>
<div class="timeline-content">
<div class="note-header">
@@ -159,7 +161,7 @@
:created-at="note.created_at"
:note-id="note.id"
action-text="commented"
- />
+ />
<note-actions
:author-id="author.id"
:note-id="note.id"
@@ -170,7 +172,7 @@
:report-abuse-path="note.report_abuse_path"
@handleEdit="editHandler"
@handleDelete="deleteHandler"
- />
+ />
</div>
<note-body
:note="note"
@@ -179,7 +181,7 @@
@handleFormUpdate="formUpdateHandler"
@cancelFormEdition="formCancelHandler"
ref="noteBody"
- />
+ />
</div>
</div>
</li>
diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue
index c4cae4b3b6f..92db4830704 100644
--- a/app/assets/javascripts/notes/components/notes_app.vue
+++ b/app/assets/javascripts/notes/components/notes_app.vue
@@ -13,7 +13,16 @@
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
export default {
- name: 'notesApp',
+ name: 'NotesApp',
+ components: {
+ noteableNote,
+ noteableDiscussion,
+ systemNote,
+ commentForm,
+ loadingIcon,
+ placeholderNote,
+ placeholderSystemNote,
+ },
props: {
noteableData: {
type: Object,
@@ -26,7 +35,7 @@
userData: {
type: Object,
required: false,
- default: {},
+ default: () => ({}),
},
},
store,
@@ -35,21 +44,30 @@
isLoading: true,
};
},
- components: {
- noteableNote,
- noteableDiscussion,
- systemNote,
- commentForm,
- loadingIcon,
- placeholderNote,
- placeholderSystemNote,
- },
computed: {
...mapGetters([
'notes',
'getNotesDataByProp',
]),
},
+ created() {
+ this.setNotesData(this.notesData);
+ this.setNoteableData(this.noteableData);
+ this.setUserData(this.userData);
+ },
+ mounted() {
+ this.fetchNotes();
+
+ const parentElement = this.$el.parentElement;
+
+ if (parentElement &&
+ parentElement.classList.contains('js-vue-notes-event')) {
+ parentElement.addEventListener('toggleAward', (event) => {
+ const { awardName, noteId } = event.detail;
+ this.actionToggleAward({ awardName, noteId });
+ });
+ }
+ },
methods: {
...mapActions({
actionFetchNotes: 'fetchNotes',
@@ -105,24 +123,6 @@
}
},
},
- created() {
- this.setNotesData(this.notesData);
- this.setNoteableData(this.noteableData);
- this.setUserData(this.userData);
- },
- mounted() {
- this.fetchNotes();
-
- const parentElement = this.$el.parentElement;
-
- if (parentElement &&
- parentElement.classList.contains('js-vue-notes-event')) {
- parentElement.addEventListener('toggleAward', (event) => {
- const { awardName, noteId } = event.detail;
- this.actionToggleAward({ awardName, noteId });
- });
- }
- },
};
</script>
@@ -144,7 +144,7 @@
:is="getComponentName(note)"
:note="getComponentData(note)"
:key="note.id"
- />
+ />
</ul>
<comment-form />
diff --git a/app/assets/javascripts/abuse_reports.js b/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js
index d2d3a257c0d..d87e6304a24 100644
--- a/app/assets/javascripts/abuse_reports.js
+++ b/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js
@@ -1,4 +1,4 @@
-import { truncate } from './lib/utils/text_utility';
+import { truncate } from '../../../lib/utils/text_utility';
const MAX_MESSAGE_LENGTH = 500;
const MESSAGE_CELL_SELECTOR = '.abuse-reports .message';
diff --git a/app/assets/javascripts/pages/admin/abuse_reports/index.js b/app/assets/javascripts/pages/admin/abuse_reports/index.js
new file mode 100644
index 00000000000..c0b6e8d4095
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/abuse_reports/index.js
@@ -0,0 +1,3 @@
+import AbuseReports from './abuse_reports';
+
+export default () => new AbuseReports();
diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/pages/admin/admin.js
index c1f7fa2aced..135c15c346b 100644
--- a/app/assets/javascripts/admin.js
+++ b/app/assets/javascripts/pages/admin/admin.js
@@ -1,4 +1,4 @@
-import { refreshCurrentPage } from './lib/utils/url_utility';
+import { refreshCurrentPage } from '../../lib/utils/url_utility';
function showBlacklistType() {
if ($('input[name="blacklist_type"]:checked').val() === 'file') {
diff --git a/app/assets/javascripts/broadcast_message.js b/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js
index ff88083a4b4..ff88083a4b4 100644
--- a/app/assets/javascripts/broadcast_message.js
+++ b/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js
diff --git a/app/assets/javascripts/pages/admin/broadcast_messages/index.js b/app/assets/javascripts/pages/admin/broadcast_messages/index.js
new file mode 100644
index 00000000000..b548c48282a
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/broadcast_messages/index.js
@@ -0,0 +1,3 @@
+import initBroadcastMessagesForm from './broadcast_message';
+
+export default () => initBroadcastMessagesForm();
diff --git a/app/assets/javascripts/pages/admin/cohorts/index.js b/app/assets/javascripts/pages/admin/cohorts/index.js
new file mode 100644
index 00000000000..42ef9d38ef7
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/cohorts/index.js
@@ -0,0 +1,3 @@
+import initUsagePing from './usage_ping';
+
+export default () => initUsagePing();
diff --git a/app/assets/javascripts/usage_ping.js b/app/assets/javascripts/pages/admin/cohorts/usage_ping.js
index 2389056bd02..2389056bd02 100644
--- a/app/assets/javascripts/usage_ping.js
+++ b/app/assets/javascripts/pages/admin/cohorts/usage_ping.js
diff --git a/app/assets/javascripts/pages/admin/conversational_development_index/show/index.js b/app/assets/javascripts/pages/admin/conversational_development_index/show/index.js
new file mode 100644
index 00000000000..6e66ef69fe1
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/conversational_development_index/show/index.js
@@ -0,0 +1,3 @@
+import UserCallout from '../../../../user_callout';
+
+export default () => new UserCallout();
diff --git a/app/assets/javascripts/pages/admin/groups/edit/index.js b/app/assets/javascripts/pages/admin/groups/edit/index.js
new file mode 100644
index 00000000000..ff9ef8d2449
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/groups/edit/index.js
@@ -0,0 +1,3 @@
+import groupAvatar from '../../../../group_avatar';
+
+export default () => groupAvatar();
diff --git a/app/assets/javascripts/pages/admin/groups/new/index.js b/app/assets/javascripts/pages/admin/groups/new/index.js
new file mode 100644
index 00000000000..fb5c46e4729
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/groups/new/index.js
@@ -0,0 +1,9 @@
+import BindInOut from '../../../../behaviors/bind_in_out';
+import Group from '../../../../group';
+import groupAvatar from '../../../../group_avatar';
+
+export default () => {
+ BindInOut.initAll();
+ new Group(); // eslint-disable-line no-new
+ groupAvatar();
+};
diff --git a/app/assets/javascripts/pages/admin/groups/show/index.js b/app/assets/javascripts/pages/admin/groups/show/index.js
new file mode 100644
index 00000000000..5defea104d4
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/groups/show/index.js
@@ -0,0 +1,3 @@
+import UsersSelect from '../../../../users_select';
+
+export default () => new UsersSelect();
diff --git a/app/assets/javascripts/pages/admin/impersonation_tokens/index.js b/app/assets/javascripts/pages/admin/impersonation_tokens/index.js
new file mode 100644
index 00000000000..030328a1363
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/impersonation_tokens/index.js
@@ -0,0 +1,3 @@
+import DueDateSelectors from '../../../due_date_select';
+
+export default () => new DueDateSelectors();
diff --git a/app/assets/javascripts/pages/admin/index.js b/app/assets/javascripts/pages/admin/index.js
new file mode 100644
index 00000000000..8b843037d85
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/index.js
@@ -0,0 +1,3 @@
+import initAdmin from './admin';
+
+export default () => initAdmin();
diff --git a/app/assets/javascripts/pages/admin/labels/edit/index.js b/app/assets/javascripts/pages/admin/labels/edit/index.js
new file mode 100644
index 00000000000..d7ec6e47f67
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/labels/edit/index.js
@@ -0,0 +1,3 @@
+import Labels from '../../../../labels';
+
+export default () => new Labels();
diff --git a/app/assets/javascripts/pages/admin/labels/new/index.js b/app/assets/javascripts/pages/admin/labels/new/index.js
new file mode 100644
index 00000000000..d7ec6e47f67
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/labels/new/index.js
@@ -0,0 +1,3 @@
+import Labels from '../../../../labels';
+
+export default () => new Labels();
diff --git a/app/assets/javascripts/pages/admin/projects/index.js b/app/assets/javascripts/pages/admin/projects/index.js
new file mode 100644
index 00000000000..71e0ddcd7b6
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/projects/index.js
@@ -0,0 +1,9 @@
+import ProjectsList from '../../../projects_list';
+import NamespaceSelect from '../../../namespace_select';
+
+export default () => {
+ new ProjectsList(); // eslint-disable-line no-new
+
+ document.querySelectorAll('.js-namespace-select')
+ .forEach(dropdown => new NamespaceSelect({ dropdown }));
+};
diff --git a/app/assets/javascripts/ci_lint_editor.js b/app/assets/javascripts/pages/ci/lints/ci_lint_editor.js
index b9469e5b7cb..b9469e5b7cb 100644
--- a/app/assets/javascripts/ci_lint_editor.js
+++ b/app/assets/javascripts/pages/ci/lints/ci_lint_editor.js
diff --git a/app/assets/javascripts/pages/ci/lints/index.js b/app/assets/javascripts/pages/ci/lints/index.js
new file mode 100644
index 00000000000..5cc66546109
--- /dev/null
+++ b/app/assets/javascripts/pages/ci/lints/index.js
@@ -0,0 +1,3 @@
+import CILintEditor from './ci_lint_editor';
+
+export default () => new CILintEditor();
diff --git a/app/assets/javascripts/pages/dashboard/activity/index.js b/app/assets/javascripts/pages/dashboard/activity/index.js
new file mode 100644
index 00000000000..95faf1f1e98
--- /dev/null
+++ b/app/assets/javascripts/pages/dashboard/activity/index.js
@@ -0,0 +1,3 @@
+import Activities from '~/activities';
+
+export default () => new Activities();
diff --git a/app/assets/javascripts/pages/dashboard/issues/index.js b/app/assets/javascripts/pages/dashboard/issues/index.js
new file mode 100644
index 00000000000..b7353669e65
--- /dev/null
+++ b/app/assets/javascripts/pages/dashboard/issues/index.js
@@ -0,0 +1,7 @@
+import projectSelect from '~/project_select';
+import initLegacyFilters from '~/init_legacy_filters';
+
+export default () => {
+ projectSelect();
+ initLegacyFilters();
+};
diff --git a/app/assets/javascripts/pages/dashboard/milestones/index/index.js b/app/assets/javascripts/pages/dashboard/milestones/index/index.js
new file mode 100644
index 00000000000..0f2f1bd4a25
--- /dev/null
+++ b/app/assets/javascripts/pages/dashboard/milestones/index/index.js
@@ -0,0 +1,3 @@
+import projectSelect from '~/project_select';
+
+export default projectSelect;
diff --git a/app/assets/javascripts/pages/dashboard/milestones/show/index.js b/app/assets/javascripts/pages/dashboard/milestones/show/index.js
new file mode 100644
index 00000000000..2e7a08a369c
--- /dev/null
+++ b/app/assets/javascripts/pages/dashboard/milestones/show/index.js
@@ -0,0 +1,7 @@
+import Milestone from '~/milestone';
+import Sidebar from '~/right_sidebar';
+
+export default () => {
+ new Milestone(); // eslint-disable-line no-new
+ new Sidebar(); // eslint-disable-line no-new
+};
diff --git a/app/assets/javascripts/pages/dashboard/projects/index.js b/app/assets/javascripts/pages/dashboard/projects/index.js
new file mode 100644
index 00000000000..c88cbf1a6ba
--- /dev/null
+++ b/app/assets/javascripts/pages/dashboard/projects/index.js
@@ -0,0 +1,3 @@
+import ProjectsList from '~/projects_list';
+
+export default () => new ProjectsList();
diff --git a/app/assets/javascripts/pages/explore/groups/index.js b/app/assets/javascripts/pages/explore/groups/index.js
new file mode 100644
index 00000000000..859b073f1cb
--- /dev/null
+++ b/app/assets/javascripts/pages/explore/groups/index.js
@@ -0,0 +1,14 @@
+import GroupsList from '~/groups_list';
+import Landing from '~/landing';
+
+export default function () {
+ new GroupsList(); // eslint-disable-line no-new
+ const landingElement = document.querySelector('.js-explore-groups-landing');
+ if (!landingElement) return;
+ const exploreGroupsLanding = new Landing(
+ landingElement,
+ landingElement.querySelector('.dismiss-button'),
+ 'explore_groups_landing_dismissed',
+ );
+ exploreGroupsLanding.toggle();
+}
diff --git a/app/assets/javascripts/pages/explore/projects/index.js b/app/assets/javascripts/pages/explore/projects/index.js
new file mode 100644
index 00000000000..c88cbf1a6ba
--- /dev/null
+++ b/app/assets/javascripts/pages/explore/projects/index.js
@@ -0,0 +1,3 @@
+import ProjectsList from '~/projects_list';
+
+export default () => new ProjectsList();
diff --git a/app/assets/javascripts/pages/import/fogbugz/new_user_map/index.js b/app/assets/javascripts/pages/import/fogbugz/new_user_map/index.js
new file mode 100644
index 00000000000..5defea104d4
--- /dev/null
+++ b/app/assets/javascripts/pages/import/fogbugz/new_user_map/index.js
@@ -0,0 +1,3 @@
+import UsersSelect from '../../../../users_select';
+
+export default () => new UsersSelect();
diff --git a/app/assets/javascripts/pages/projects/activity/index.js b/app/assets/javascripts/pages/projects/activity/index.js
new file mode 100644
index 00000000000..7af95127fd5
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/activity/index.js
@@ -0,0 +1,7 @@
+import Activities from '~/activities';
+import ShortcutsNavigation from '~/shortcuts_navigation';
+
+export default function () {
+ new Activities(); // eslint-disable-line no-new
+ new ShortcutsNavigation(); // eslint-disable-line no-new
+}
diff --git a/app/assets/javascripts/pages/projects/artifacts/browse/index.js b/app/assets/javascripts/pages/projects/artifacts/browse/index.js
new file mode 100644
index 00000000000..02456071086
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/artifacts/browse/index.js
@@ -0,0 +1,7 @@
+import BuildArtifacts from '~/build_artifacts';
+import ShortcutsNavigation from '~/shortcuts_navigation';
+
+export default function () {
+ new ShortcutsNavigation(); // eslint-disable-line no-new
+ new BuildArtifacts(); // eslint-disable-line no-new
+}
diff --git a/app/assets/javascripts/pages/projects/artifacts/file/index.js b/app/assets/javascripts/pages/projects/artifacts/file/index.js
new file mode 100644
index 00000000000..4cd67ac76e3
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/artifacts/file/index.js
@@ -0,0 +1,7 @@
+import BlobViewer from '~/blob/viewer/index';
+import ShortcutsNavigation from '~/shortcuts_navigation';
+
+export default function () {
+ new ShortcutsNavigation(); // eslint-disable-line no-new
+ new BlobViewer(); // eslint-disable-line no-new
+}
diff --git a/app/assets/javascripts/pages/search/show/index.js b/app/assets/javascripts/pages/search/show/index.js
new file mode 100644
index 00000000000..4264c5c9dbe
--- /dev/null
+++ b/app/assets/javascripts/pages/search/show/index.js
@@ -0,0 +1,3 @@
+import Search from './search';
+
+export default () => new Search();
diff --git a/app/assets/javascripts/search.js b/app/assets/javascripts/pages/search/show/search.js
index 363322af47a..d44195f6b72 100644
--- a/app/assets/javascripts/search.js
+++ b/app/assets/javascripts/pages/search/show/search.js
@@ -1,5 +1,5 @@
-import Flash from './flash';
-import Api from './api';
+import Flash from '~/flash';
+import Api from '~/api';
export default class Search {
constructor() {
diff --git a/app/assets/javascripts/pages/sessions/new/index.js b/app/assets/javascripts/pages/sessions/new/index.js
new file mode 100644
index 00000000000..f163557babc
--- /dev/null
+++ b/app/assets/javascripts/pages/sessions/new/index.js
@@ -0,0 +1,11 @@
+import UsernameValidator from './username_validator';
+import SigninTabsMemoizer from './signin_tabs_memoizer';
+import OAuthRememberMe from './oauth_remember_me';
+
+export default () => {
+ new UsernameValidator(); // eslint-disable-line no-new
+ new SigninTabsMemoizer(); // eslint-disable-line no-new
+ new OAuthRememberMe({ // eslint-disable-line no-new
+ container: $('.omniauth-container'),
+ }).bindEvents();
+};
diff --git a/app/assets/javascripts/oauth_remember_me.js b/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js
index ffc2dd6bbca..ffc2dd6bbca 100644
--- a/app/assets/javascripts/oauth_remember_me.js
+++ b/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js
diff --git a/app/assets/javascripts/signin_tabs_memoizer.js b/app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js
index 20255398047..f99573e5c74 100644
--- a/app/assets/javascripts/signin_tabs_memoizer.js
+++ b/app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js
@@ -1,6 +1,6 @@
/* eslint no-param-reassign: ["error", { "props": false }]*/
/* eslint no-new: "off" */
-import AccessorUtilities from './lib/utils/accessor';
+import AccessorUtilities from '~/lib/utils/accessor';
/**
* Memorize the last selected tab after reloading a page.
diff --git a/app/assets/javascripts/username_validator.js b/app/assets/javascripts/pages/sessions/new/username_validator.js
index bb34d5d2008..bb34d5d2008 100644
--- a/app/assets/javascripts/username_validator.js
+++ b/app/assets/javascripts/pages/sessions/new/username_validator.js
diff --git a/app/assets/javascripts/pages/snippets/show/index.js b/app/assets/javascripts/pages/snippets/show/index.js
new file mode 100644
index 00000000000..04c9562bfbb
--- /dev/null
+++ b/app/assets/javascripts/pages/snippets/show/index.js
@@ -0,0 +1,12 @@
+/* eslint-disable no-new */
+import LineHighlighter from '../../../line_highlighter';
+import BlobViewer from '../../../blob/viewer';
+import ZenMode from '../../../zen_mode';
+import initNotes from '../../../init_notes';
+
+export default () => {
+ new LineHighlighter();
+ new BlobViewer();
+ initNotes();
+ new ZenMode();
+};
diff --git a/app/assets/javascripts/pdf/index.vue b/app/assets/javascripts/pdf/index.vue
index c8a2f778ee8..00f32d9de78 100644
--- a/app/assets/javascripts/pdf/index.vue
+++ b/app/assets/javascripts/pdf/index.vue
@@ -5,6 +5,7 @@
import page from './page/index.vue';
export default {
+ components: { page },
props: {
pdf: {
type: [String, Uint8Array],
@@ -17,8 +18,6 @@
pages: [],
};
},
- components: { page },
- watch: { pdf: 'load' },
computed: {
document() {
return typeof this.pdf === 'string' ? this.pdf : { data: this.pdf };
@@ -27,6 +26,11 @@
return this.pdf && this.pdf.length > 0;
},
},
+ watch: { pdf: 'load' },
+ mounted() {
+ pdfjsLib.PDFJS.workerSrc = workerSrc;
+ if (this.hasPDF) this.load();
+ },
methods: {
load() {
this.pages = [];
@@ -47,20 +51,20 @@
return Promise.all(pagePromises);
},
},
- mounted() {
- pdfjsLib.PDFJS.workerSrc = workerSrc;
- if (this.hasPDF) this.load();
- },
};
</script>
<template>
- <div class="pdf-viewer" v-if="hasPDF">
- <page v-for="(page, index) in pages"
+ <div
+ class="pdf-viewer"
+ v-if="hasPDF">
+ <page
+ v-for="(page, index) in pages"
:key="index"
:v-if="!loading"
:page="page"
- :number="index + 1" />
+ :number="index + 1"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/pdf/page/index.vue b/app/assets/javascripts/pdf/page/index.vue
index be38f7cc129..fcba819beba 100644
--- a/app/assets/javascripts/pdf/page/index.vue
+++ b/app/assets/javascripts/pdf/page/index.vue
@@ -45,24 +45,26 @@
<canvas
class="pdf-page"
ref="canvas"
- :data-page="number" />
+ :data-page="number"
+ >
+ </canvas>
</template>
<style>
-.pdf-page {
- margin: 8px auto 0 auto;
- border-top: 1px #ddd solid;
- border-bottom: 1px #ddd solid;
- width: 100%;
-}
+ .pdf-page {
+ margin: 8px auto 0 auto;
+ border-top: 1px #ddd solid;
+ border-bottom: 1px #ddd solid;
+ width: 100%;
+ }
-.pdf-page:first-child {
- margin-top: 0px;
- border-top: 0px;
-}
+ .pdf-page:first-child {
+ margin-top: 0px;
+ border-top: 0px;
+ }
-.pdf-page:last-child {
- margin-bottom: 0px;
- border-bottom: 0px;
-}
+ .pdf-page:last-child {
+ margin-bottom: 0px;
+ border-bottom: 0px;
+ }
</style>
diff --git a/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue b/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue
index b5d85299cf8..2d18fa2044b 100644
--- a/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue
+++ b/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue
@@ -32,6 +32,20 @@
return !!(this.customInputEnabled || !this.intervalIsPreset);
},
},
+ watch: {
+ cronInterval() {
+ // updates field validation state when model changes, as
+ // glFieldError only updates on input.
+ this.$nextTick(() => {
+ gl.pipelineScheduleFieldErrors.updateFormValidityState();
+ });
+ },
+ },
+ created() {
+ if (this.intervalIsPreset) {
+ this.enableCustomInput = false;
+ }
+ },
methods: {
toggleCustomInput(shouldEnable) {
this.customInputEnabled = shouldEnable;
@@ -43,20 +57,6 @@
}
},
},
- created() {
- if (this.intervalIsPreset) {
- this.enableCustomInput = false;
- }
- },
- watch: {
- cronInterval() {
- // updates field validation state when model changes, as
- // glFieldError only updates on input.
- this.$nextTick(() => {
- gl.pipelineScheduleFieldErrors.updateFormValidityState();
- });
- },
- },
};
</script>
@@ -78,7 +78,12 @@
</label>
<span class="cron-syntax-link-wrap">
- (<a :href="cronSyntaxUrl" target="_blank">{{ __('Cron syntax') }}</a>)
+ (<a
+ :href="cronSyntaxUrl"
+ target="_blank"
+ >
+ {{ __('Cron syntax') }}
+ </a>)
</span>
</div>
@@ -93,7 +98,10 @@
@click="toggleCustomInput(false)"
/>
- <label class="label-light" for="every-day">
+ <label
+ class="label-light"
+ for="every-day"
+ >
{{ __('Every day (at 4:00am)') }}
</label>
</div>
@@ -109,7 +117,10 @@
@click="toggleCustomInput(false)"
/>
- <label class="label-light" for="every-week">
+ <label
+ class="label-light"
+ for="every-week"
+ >
{{ __('Every week (Sundays at 4:00am)') }}
</label>
</div>
@@ -125,7 +136,10 @@
@click="toggleCustomInput(false)"
/>
- <label class="label-light" for="every-month">
+ <label
+ class="label-light"
+ for="every-month"
+ >
{{ __('Every month (on the 1st at 4:00am)') }}
</label>
</div>
diff --git a/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules_callout.vue b/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules_callout.vue
index 6e0bc2d697a..aa04a0ac47a 100644
--- a/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules_callout.vue
+++ b/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules_callout.vue
@@ -16,15 +16,15 @@
calloutDismissed: Cookies.get(cookieKey) === 'true',
};
},
+ created() {
+ this.illustrationSvg = illustrationSvg;
+ },
methods: {
dismissCallout() {
this.calloutDismissed = true;
Cookies.set(cookieKey, this.calloutDismissed, { expires: 365 });
},
},
- created() {
- this.illustrationSvg = illustrationSvg;
- },
};
</script>
<template>
@@ -41,17 +41,25 @@
class="fa fa-times">
</i>
</button>
- <div class="svg-container" v-html="illustrationSvg"></div>
+ <div
+ class="svg-container"
+ v-html="illustrationSvg">
+ </div>
<div class="user-callout-copy">
<h4>{{ __('Scheduling Pipelines') }}</h4>
<p>
- {{ __('The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.') }}
+ {{ __(`The pipelines schedule runs pipelines in the future,
+repeatedly, for specific branches or tags.
+Those scheduled pipelines will inherit limited project access based on their associated user.`) }}
</p>
<p> {{ __('Learn more in the') }}
<a
:href="docsUrl"
target="_blank"
- rel="nofollow">{{ s__('Learn more in the|pipeline schedules documentation') }}</a>. <!-- oneline to prevent extra space before period -->
+ rel="nofollow"
+ >
+ {{ s__('Learn more in the|pipeline schedules documentation') }}</a>.
+ <!-- oneline to prevent extra space before period -->
</p>
</div>
</div>
diff --git a/app/assets/javascripts/pipelines/components/async_button.vue b/app/assets/javascripts/pipelines/components/async_button.vue
index 16cc0761fc1..4ad3f66ee8c 100644
--- a/app/assets/javascripts/pipelines/components/async_button.vue
+++ b/app/assets/javascripts/pipelines/components/async_button.vue
@@ -1,67 +1,68 @@
<script>
-/* eslint-disable no-new, no-alert */
+ /* eslint-disable no-alert */
-import eventHub from '../event_hub';
-import loadingIcon from '../../vue_shared/components/loading_icon.vue';
-import tooltip from '../../vue_shared/directives/tooltip';
+ import eventHub from '../event_hub';
+ import loadingIcon from '../../vue_shared/components/loading_icon.vue';
+ import tooltip from '../../vue_shared/directives/tooltip';
-export default {
- props: {
- endpoint: {
- type: String,
- required: true,
+ export default {
+ directives: {
+ tooltip,
},
- title: {
- type: String,
- required: true,
+ components: {
+ loadingIcon,
},
- icon: {
- type: String,
- required: true,
+ props: {
+ endpoint: {
+ type: String,
+ required: true,
+ },
+ title: {
+ type: String,
+ required: true,
+ },
+ icon: {
+ type: String,
+ required: true,
+ },
+ cssClass: {
+ type: String,
+ required: true,
+ },
+ confirmActionMessage: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
- cssClass: {
- type: String,
- required: true,
+ data() {
+ return {
+ isLoading: false,
+ };
},
- confirmActionMessage: {
- type: String,
- required: false,
+ computed: {
+ iconClass() {
+ return `fa fa-${this.icon}`;
+ },
+ buttonClass() {
+ return `btn ${this.cssClass}`;
+ },
},
- },
- directives: {
- tooltip,
- },
- components: {
- loadingIcon,
- },
- data() {
- return {
- isLoading: false,
- };
- },
- computed: {
- iconClass() {
- return `fa fa-${this.icon}`;
- },
- buttonClass() {
- return `btn ${this.cssClass}`;
- },
- },
- methods: {
- onClick() {
- if (this.confirmActionMessage && confirm(this.confirmActionMessage)) {
- this.makeRequest();
- } else if (!this.confirmActionMessage) {
- this.makeRequest();
- }
- },
- makeRequest() {
- this.isLoading = true;
+ methods: {
+ onClick() {
+ if (this.confirmActionMessage !== '' && confirm(this.confirmActionMessage)) {
+ this.makeRequest();
+ } else if (this.confirmActionMessage === '') {
+ this.makeRequest();
+ }
+ },
+ makeRequest() {
+ this.isLoading = true;
- eventHub.$emit('postAction', this.endpoint);
+ eventHub.$emit('postAction', this.endpoint);
+ },
},
- },
-};
+ };
</script>
<template>
diff --git a/app/assets/javascripts/pipelines/components/empty_state.vue b/app/assets/javascripts/pipelines/components/empty_state.vue
index 78322f30685..dfaa2574091 100644
--- a/app/assets/javascripts/pipelines/components/empty_state.vue
+++ b/app/assets/javascripts/pipelines/components/empty_state.vue
@@ -26,13 +26,15 @@
{{ s__("Pipelines|Build with confidence") }}
</h4>
<p>
- {{ s__("Pipelines|Continous Integration can help catch bugs by running your tests automatically, while Continuous Deployment can help you deliver code to your product environment.") }}
+ {{ s__(`Pipelines|Continous Integration can help
+catch bugs by running your tests automatically,
+while Continuous Deployment can help you deliver code to your product environment.`) }}
</p>
<div class="text-center">
<a
:href="helpPagePath"
class="btn btn-info"
- >
+ >
{{ s__("Pipelines|Get started with Pipelines") }}
</a>
</div>
diff --git a/app/assets/javascripts/pipelines/components/graph/action_component.vue b/app/assets/javascripts/pipelines/components/graph/action_component.vue
index 19d8e1f49cf..d7effb27bff 100644
--- a/app/assets/javascripts/pipelines/components/graph/action_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/action_component.vue
@@ -7,6 +7,14 @@
* TODO: Remove UJS from here and use an async request instead.
*/
export default {
+ components: {
+ icon,
+ },
+
+ directives: {
+ tooltip,
+ },
+
props: {
tooltipText: {
type: String,
@@ -29,14 +37,6 @@
},
},
- components: {
- icon,
- },
-
- directives: {
- tooltip,
- },
-
computed: {
cssClass() {
const actionIconDash = dasherize(this.actionIcon);
@@ -53,7 +53,8 @@
:href="link"
class="ci-action-icon-container ci-action-icon-wrapper"
:class="cssClass"
- data-container="body">
- <icon :name="actionIcon"/>
+ data-container="body"
+ >
+ <icon :name="actionIcon" />
</a>
</template>
diff --git a/app/assets/javascripts/pipelines/components/graph/dropdown_action_component.vue b/app/assets/javascripts/pipelines/components/graph/dropdown_action_component.vue
index 1c0944d45fc..7c4fd65e36f 100644
--- a/app/assets/javascripts/pipelines/components/graph/dropdown_action_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/dropdown_action_component.vue
@@ -7,6 +7,13 @@
* TODO: Remove UJS from here and use an async request instead.
*/
export default {
+ components: {
+ icon,
+ },
+
+ directives: {
+ tooltip,
+ },
props: {
tooltipText: {
type: String,
@@ -28,14 +35,6 @@
required: true,
},
},
-
- components: {
- icon,
- },
-
- directives: {
- tooltip,
- },
};
</script>
<template>
@@ -47,7 +46,8 @@
rel="nofollow"
class="ci-action-icon-wrapper js-ci-status-icon"
data-container="body"
- aria-label="Job's action">
- <icon :name="actionIcon"/>
+ aria-label="Job's action"
+ >
+ <icon :name="actionIcon" />
</a>
</template>
diff --git a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue
index 7006d05e7b2..b86e95f0b4a 100644
--- a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue
@@ -27,13 +27,6 @@
* }
*/
export default {
- props: {
- job: {
- type: Object,
- required: true,
- },
- },
-
directives: {
tooltip,
},
@@ -43,12 +36,23 @@
jobNameComponent,
},
+ props: {
+ job: {
+ type: Object,
+ required: true,
+ },
+ },
+
computed: {
tooltipText() {
return `${this.job.name} - ${this.job.status.label}`;
},
},
+ mounted() {
+ this.stopDropdownClickPropagation();
+ },
+
methods: {
/**
* When the user right clicks or cmd/ctrl + click in the job name
@@ -59,16 +63,13 @@
* target the click event of this component.
*/
stopDropdownClickPropagation() {
- $(this.$el.querySelectorAll('.js-grouped-pipeline-dropdown a.mini-pipeline-graph-dropdown-item'))
+ $(this.$el
+ .querySelectorAll('.js-grouped-pipeline-dropdown a.mini-pipeline-graph-dropdown-item'))
.on('click', (e) => {
e.stopPropagation();
});
},
},
-
- mounted() {
- this.stopDropdownClickPropagation();
- },
};
</script>
<template>
@@ -83,22 +84,25 @@
<job-name-component
:name="job.name"
- :status="job.status" />
+ :status="job.status"
+ />
<span class="dropdown-counter-badge">
- {{job.size}}
+ {{ job.size }}
</span>
</button>
<ul class="dropdown-menu big-pipeline-graph-dropdown-menu js-grouped-pipeline-dropdown">
<li class="scrollable-menu">
<ul>
- <li v-for="item in job.jobs">
+ <li
+ v-for="(item, i) in job.jobs"
+ :key="i">
<job-component
:job="item"
:is-dropdown="true"
css-class-job-name="mini-pipeline-graph-dropdown-item"
- />
+ />
</li>
</ul>
</li>
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
index 66bc1d1979c..a1f58580318 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
@@ -1,9 +1,13 @@
<script>
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
- import '~/flash';
import stageColumnComponent from './stage_column_component.vue';
export default {
+ components: {
+ stageColumnComponent,
+ loadingIcon,
+ },
+
props: {
isLoading: {
type: Boolean,
@@ -15,11 +19,6 @@
},
},
- components: {
- stageColumnComponent,
- loadingIcon,
- },
-
computed: {
graph() {
return this.pipeline.details && this.pipeline.details.stages;
@@ -58,7 +57,7 @@
<loading-icon
v-if="isLoading"
size="3"
- />
+ />
</div>
<ul
@@ -70,7 +69,8 @@
:jobs="stage.groups"
:key="stage.name"
:stage-connector-class="stageConnectorClass(index, stage)"
- :is-first-column="isFirstColumn(index)"/>
+ :is-first-column="isFirstColumn(index)"
+ />
</ul>
</div>
</div>
diff --git a/app/assets/javascripts/pipelines/components/graph/job_component.vue b/app/assets/javascripts/pipelines/components/graph/job_component.vue
index b01c799643c..9b136573135 100644
--- a/app/assets/javascripts/pipelines/components/graph/job_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/job_component.vue
@@ -29,6 +29,15 @@
*/
export default {
+ components: {
+ actionComponent,
+ dropdownActionComponent,
+ jobNameComponent,
+ },
+
+ directives: {
+ tooltip,
+ },
props: {
job: {
type: Object,
@@ -48,16 +57,6 @@
},
},
- components: {
- actionComponent,
- dropdownActionComponent,
- jobNameComponent,
- },
-
- directives: {
- tooltip,
- },
-
computed: {
status() {
return this.job && this.job.status ? this.job.status : {};
@@ -102,12 +101,12 @@
:class="cssClassJobName"
data-container="body"
class="js-pipeline-graph-job-link"
- >
+ >
<job-name-component
:name="job.name"
:status="job.status"
- />
+ />
</a>
<div
@@ -117,12 +116,12 @@
:title="tooltipText"
:class="cssClassJobName"
data-container="body"
- >
+ >
<job-name-component
:name="job.name"
:status="job.status"
- />
+ />
</div>
<action-component
@@ -131,7 +130,7 @@
:link="status.action.path"
:action-icon="status.action.icon"
:action-method="status.action.method"
- />
+ />
<dropdown-action-component
v-if="hasAction && isDropdown"
@@ -139,6 +138,6 @@
:link="status.action.path"
:action-icon="status.action.icon"
:action-method="status.action.method"
- />
+ />
</div>
</template>
diff --git a/app/assets/javascripts/pipelines/components/graph/job_name_component.vue b/app/assets/javascripts/pipelines/components/graph/job_name_component.vue
index f46d21bd6d7..14f4964a406 100644
--- a/app/assets/javascripts/pipelines/components/graph/job_name_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/job_name_component.vue
@@ -8,6 +8,9 @@
* - Dropdown badge components
*/
export default {
+ components: {
+ ciIcon,
+ },
props: {
name: {
type: String,
@@ -19,19 +22,14 @@
required: true,
},
},
-
- components: {
- ciIcon,
- },
};
</script>
<template>
<span class="ci-job-name-component">
- <ci-icon
- :status="status" />
+ <ci-icon :status="status" />
<span class="ci-status-text">
- {{name}}
+ {{ name }}
</span>
</span>
</template>
diff --git a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
index 9b1bbb0906f..e027f08ff5c 100644
--- a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
@@ -1,58 +1,58 @@
<script>
-import jobComponent from './job_component.vue';
-import dropdownJobComponent from './dropdown_job_component.vue';
+ import jobComponent from './job_component.vue';
+ import dropdownJobComponent from './dropdown_job_component.vue';
-export default {
- props: {
- title: {
- type: String,
- required: true,
+ export default {
+ components: {
+ jobComponent,
+ dropdownJobComponent,
},
- jobs: {
- type: Array,
- required: true,
- },
-
- isFirstColumn: {
- type: Boolean,
- required: false,
- default: false,
- },
+ props: {
+ title: {
+ type: String,
+ required: true,
+ },
- stageConnectorClass: {
- type: String,
- required: false,
- default: '',
- },
- },
+ jobs: {
+ type: Array,
+ required: true,
+ },
- components: {
- jobComponent,
- dropdownJobComponent,
- },
+ isFirstColumn: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
- methods: {
- firstJob(list) {
- return list[0];
+ stageConnectorClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
- jobId(job) {
- return `ci-badge-${job.name}`;
- },
+ methods: {
+ firstJob(list) {
+ return list[0];
+ },
+
+ jobId(job) {
+ return `ci-badge-${job.name}`;
+ },
- buildConnnectorClass(index) {
- return index === 0 && !this.isFirstColumn ? 'left-connector' : '';
+ buildConnnectorClass(index) {
+ return index === 0 && !this.isFirstColumn ? 'left-connector' : '';
+ },
},
- },
-};
+ };
</script>
<template>
<li
class="stage-column"
:class="stageConnectorClass">
<div class="stage-name">
- {{title}}
+ {{ title }}
</div>
<div class="builds-container">
<ul>
@@ -61,7 +61,8 @@ export default {
:key="job.id"
class="build"
:class="buildConnnectorClass(index)"
- :id="jobId(job)">
+ :id="jobId(job)"
+ >
<div class="curve"></div>
@@ -69,12 +70,12 @@ export default {
v-if="job.size === 1"
:job="job"
css-class-job-name="build-content"
- />
+ />
<dropdown-job-component
v-if="job.size > 1"
:job="job"
- />
+ />
</li>
</ul>
diff --git a/app/assets/javascripts/pipelines/components/header_component.vue b/app/assets/javascripts/pipelines/components/header_component.vue
index 2a1ecac3707..942acc8c412 100644
--- a/app/assets/javascripts/pipelines/components/header_component.vue
+++ b/app/assets/javascripts/pipelines/components/header_component.vue
@@ -1,82 +1,81 @@
<script>
-import ciHeader from '../../vue_shared/components/header_ci_component.vue';
-import eventHub from '../event_hub';
-import loadingIcon from '../../vue_shared/components/loading_icon.vue';
+ import ciHeader from '../../vue_shared/components/header_ci_component.vue';
+ import eventHub from '../event_hub';
+ import loadingIcon from '../../vue_shared/components/loading_icon.vue';
-export default {
- name: 'PipelineHeaderSection',
- props: {
- pipeline: {
- type: Object,
- required: true,
+ export default {
+ name: 'PipelineHeaderSection',
+ components: {
+ ciHeader,
+ loadingIcon,
},
- isLoading: {
- type: Boolean,
- required: true,
+ props: {
+ pipeline: {
+ type: Object,
+ required: true,
+ },
+ isLoading: {
+ type: Boolean,
+ required: true,
+ },
},
- },
- components: {
- ciHeader,
- loadingIcon,
- },
-
- data() {
- return {
- actions: this.getActions(),
- };
- },
-
- computed: {
- status() {
- return this.pipeline.details && this.pipeline.details.status;
+ data() {
+ return {
+ actions: this.getActions(),
+ };
},
- shouldRenderContent() {
- return !this.isLoading && Object.keys(this.pipeline).length;
+
+ computed: {
+ status() {
+ return this.pipeline.details && this.pipeline.details.status;
+ },
+ shouldRenderContent() {
+ return !this.isLoading && Object.keys(this.pipeline).length;
+ },
},
- },
- methods: {
- postAction(action) {
- const index = this.actions.indexOf(action);
+ watch: {
+ pipeline() {
+ this.actions = this.getActions();
+ },
+ },
- this.$set(this.actions[index], 'isLoading', true);
+ methods: {
+ postAction(action) {
+ const index = this.actions.indexOf(action);
- eventHub.$emit('headerPostAction', action);
- },
+ this.$set(this.actions[index], 'isLoading', true);
- getActions() {
- const actions = [];
+ eventHub.$emit('headerPostAction', action);
+ },
- if (this.pipeline.retry_path) {
- actions.push({
- label: 'Retry',
- path: this.pipeline.retry_path,
- cssClass: 'js-retry-button btn btn-inverted-secondary',
- type: 'button',
- isLoading: false,
- });
- }
+ getActions() {
+ const actions = [];
- if (this.pipeline.cancel_path) {
- actions.push({
- label: 'Cancel running',
- path: this.pipeline.cancel_path,
- cssClass: 'js-btn-cancel-pipeline btn btn-danger',
- type: 'button',
- isLoading: false,
- });
- }
+ if (this.pipeline.retry_path) {
+ actions.push({
+ label: 'Retry',
+ path: this.pipeline.retry_path,
+ cssClass: 'js-retry-button btn btn-inverted-secondary',
+ type: 'button',
+ isLoading: false,
+ });
+ }
- return actions;
- },
- },
+ if (this.pipeline.cancel_path) {
+ actions.push({
+ label: 'Cancel running',
+ path: this.pipeline.cancel_path,
+ cssClass: 'js-btn-cancel-pipeline btn btn-danger',
+ type: 'button',
+ isLoading: false,
+ });
+ }
- watch: {
- pipeline() {
- this.actions = this.getActions();
+ return actions;
+ },
},
- },
-};
+ };
</script>
<template>
<div class="pipeline-header-container">
@@ -89,9 +88,10 @@ export default {
:user="pipeline.user"
:actions="actions"
@actionClicked="postAction"
- />
+ />
<loading-icon
v-if="isLoading"
- size="2"/>
+ size="2"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipeline_url.vue
index 9da0aac50a1..ceb4d9ca604 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_url.vue
+++ b/app/assets/javascripts/pipelines/components/pipeline_url.vue
@@ -4,6 +4,13 @@
import popover from '../../vue_shared/directives/popover';
export default {
+ components: {
+ userAvatarLink,
+ },
+ directives: {
+ tooltip,
+ popover,
+ },
props: {
pipeline: {
type: Object,
@@ -14,13 +21,6 @@
required: true,
},
},
- components: {
- userAvatarLink,
- },
- directives: {
- tooltip,
- popover,
- },
computed: {
user() {
return this.pipeline.user;
@@ -30,8 +30,16 @@
html: true,
trigger: 'focus',
placement: 'top',
- title: '<div class="autodevops-title">This pipeline makes use of a predefined CI/CD configuration enabled by <b>Auto DevOps.</b></div>',
- content: `<a class="autodevops-link" href="${this.autoDevopsHelpPath}" target="_blank" rel="noopener noreferrer nofollow">Learn more about Auto DevOps</a>`,
+ title: `<div class="autodevops-title">
+ This pipeline makes use of a predefined CI/CD configuration enabled by <b>Auto DevOps.</b>
+ </div>`,
+ content: `<a
+ class="autodevops-link"
+ href="${this.autoDevopsHelpPath}"
+ target="_blank"
+ rel="noopener noreferrer nofollow">
+ Learn more about Auto DevOps
+ </a>`,
};
},
},
@@ -42,7 +50,7 @@
<a
:href="pipeline.path"
class="js-pipeline-url-link">
- <span class="pipeline-id">#{{pipeline.id}}</span>
+ <span class="pipeline-id">#{{ pipeline.id }}</span>
</a>
<span>by</span>
<user-avatar-link
diff --git a/app/assets/javascripts/pipelines/components/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines.vue
index 8fa416168e7..90930d5ff44 100644
--- a/app/assets/javascripts/pipelines/components/pipelines.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines.vue
@@ -13,6 +13,15 @@
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
export default {
+ components: {
+ tablePagination,
+ navigationTabs,
+ navigationControls,
+ },
+ mixins: [
+ pipelinesMixin,
+ CIPaginationMixin,
+ ],
props: {
store: {
type: Object,
@@ -28,15 +37,6 @@
default: 'root',
},
},
- components: {
- tablePagination,
- navigationTabs,
- navigationControls,
- },
- mixins: [
- pipelinesMixin,
- CIPaginationMixin,
- ],
data() {
const pipelinesData = document.querySelector('#pipelines-list-vue').dataset;
@@ -197,7 +197,8 @@
<div class="pipelines-container">
<div
class="top-area scrolling-tabs-container inner-page-scroll-tabs"
- v-if="!shouldRenderEmptyState">
+ v-if="!shouldRenderEmptyState"
+ >
<div class="fade-left">
<i
class="fa fa-angle-left"
@@ -215,16 +216,16 @@
:tabs="tabs"
@onChangeTab="onChangeTab"
scope="pipelines"
- />
+ />
<navigation-controls
:new-pipeline-path="newPipelinePath"
:has-ci-enabled="hasCiEnabled"
:help-page-path="helpPagePath"
- :resetCachePath="resetCachePath"
+ :reset-cache-path="resetCachePath"
:ci-lint-path="ciLintPath"
:can-create-pipeline="canCreatePipelineParsed "
- />
+ />
</div>
<div class="content-list pipelines">
@@ -234,22 +235,23 @@
size="3"
v-if="isLoading"
class="prepend-top-20"
- />
+ />
<empty-state
v-if="shouldRenderEmptyState"
:help-page-path="helpPagePath"
:empty-state-svg-path="emptyStateSvgPath"
- />
+ />
<error-state
v-if="shouldRenderErrorState"
:error-state-svg-path="errorStateSvgPath"
- />
+ />
<div
class="blank-state-row"
- v-if="shouldRenderNoPipelinesMessage">
+ v-if="shouldRenderNoPipelinesMessage"
+ >
<div class="blank-state-center">
<h2 class="blank-state-title js-blank-state-title">No pipelines to show.</h2>
</div>
@@ -257,21 +259,22 @@
<div
class="table-holder"
- v-if="shouldRenderTable">
+ v-if="shouldRenderTable"
+ >
<pipelines-table-component
:pipelines="state.pipelines"
:update-graph-dropdown="updateGraphDropdown"
:auto-devops-help-path="autoDevopsPath"
:view-type="viewType"
- />
+ />
</div>
<table-pagination
v-if="shouldRenderPagination"
:change="onChangePage"
:page-info="state.pageInfo"
- />
+ />
</div>
</div>
</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_actions.vue
index f3c0aca17ba..efda36c12d6 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_actions.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_actions.vue
@@ -5,18 +5,18 @@
import tooltip from '../../vue_shared/directives/tooltip';
export default {
- props: {
- actions: {
- type: Array,
- required: true,
- },
- },
directives: {
tooltip,
},
components: {
loadingIcon,
},
+ props: {
+ actions: {
+ type: Array,
+ required: true,
+ },
+ },
data() {
return {
playIconSvg,
@@ -50,7 +50,8 @@
data-toggle="dropdown"
data-placement="top"
aria-label="Manual job"
- :disabled="isLoading">
+ :disabled="isLoading"
+ >
<span v-html="playIconSvg"></span>
<i
class="fa fa-caret-down"
@@ -60,14 +61,18 @@
</button>
<ul class="dropdown-menu dropdown-menu-align-right">
- <li v-for="action in actions">
+ <li
+ v-for="(action, i) in actions"
+ :key="i"
+ >
<button
type="button"
class="js-pipeline-action-link no-btn btn"
@click="onClickAction(action.path)"
:class="{ disabled: isActionDisabled(action) }"
- :disabled="isActionDisabled(action)">
- {{action.name}}
+ :disabled="isActionDisabled(action)"
+ >
+ {{ action.name }}
</button>
</li>
</ul>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue b/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue
index 831aa92ac4f..1b9e0f917a4 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue
@@ -3,46 +3,50 @@
import icon from '../../vue_shared/components/icon.vue';
export default {
- props: {
- artifacts: {
- type: Array,
- required: true,
- },
- },
directives: {
tooltip,
},
components: {
icon,
},
+ props: {
+ artifacts: {
+ type: Array,
+ required: true,
+ },
+ },
};
</script>
<template>
<div
class="btn-group"
- role="group">
+ role="group"
+ >
<button
v-tooltip
class="dropdown-toggle btn btn-default build-artifacts js-pipeline-dropdown-download"
title="Artifacts"
data-placement="top"
data-toggle="dropdown"
- aria-label="Artifacts">
- <icon
- name="download">
- </icon>
+ aria-label="Artifacts"
+ >
+ <icon name="download" />
<i
class="fa fa-caret-down"
- aria-hidden="true">
+ aria-hidden="true"
+ >
</i>
</button>
<ul class="dropdown-menu dropdown-menu-align-right">
- <li v-for="artifact in artifacts">
+ <li
+ v-for="(artifact, i) in artifacts"
+ :key="i">
<a
rel="nofollow"
download
- :href="artifact.path">
- Download {{artifact.name}} artifacts
+ :href="artifact.path"
+ >
+ Download {{ artifact.name }} artifacts
</a>
</li>
</ul>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_table.vue
index 16a705cbaff..c6638cdcf1e 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_table.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_table.vue
@@ -7,6 +7,9 @@
* Given an array of objects, renders a table.
*/
export default {
+ components: {
+ pipelinesTableRowComponent,
+ },
props: {
pipelines: {
type: Array,
@@ -26,34 +29,36 @@
required: true,
},
},
- components: {
- pipelinesTableRowComponent,
- },
};
</script>
<template>
<div class="ci-table">
<div
class="gl-responsive-table-row table-row-header"
- role="row">
+ role="row"
+ >
<div
class="table-section section-10 js-pipeline-status pipeline-status"
- role="rowheader">
+ role="rowheader"
+ >
Status
</div>
<div
class="table-section section-15 js-pipeline-info pipeline-info"
- role="rowheader">
+ role="rowheader"
+ >
Pipeline
</div>
<div
class="table-section section-25 js-pipeline-commit pipeline-commit"
- role="rowheader">
+ role="rowheader"
+ >
Commit
</div>
<div
class="table-section section-15 js-pipeline-stages pipeline-stages"
- role="rowheader">
+ role="rowheader"
+ >
Stages
</div>
</div>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
index 33fbce993b2..670b777199c 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
@@ -1,227 +1,228 @@
<script>
-/* eslint-disable no-param-reassign */
-import asyncButtonComponent from './async_button.vue';
-import pipelinesActionsComponent from './pipelines_actions.vue';
-import pipelinesArtifactsComponent from './pipelines_artifacts.vue';
-import ciBadge from '../../vue_shared/components/ci_badge_link.vue';
-import pipelineStage from './stage.vue';
-import pipelineUrl from './pipeline_url.vue';
-import pipelinesTimeago from './time_ago.vue';
-import commitComponent from '../../vue_shared/components/commit.vue';
+ /* eslint-disable no-param-reassign */
+ import asyncButtonComponent from './async_button.vue';
+ import pipelinesActionsComponent from './pipelines_actions.vue';
+ import pipelinesArtifactsComponent from './pipelines_artifacts.vue';
+ import ciBadge from '../../vue_shared/components/ci_badge_link.vue';
+ import pipelineStage from './stage.vue';
+ import pipelineUrl from './pipeline_url.vue';
+ import pipelinesTimeago from './time_ago.vue';
+ import commitComponent from '../../vue_shared/components/commit.vue';
-/**
- * Pipeline table row.
- *
- * Given the received object renders a table row in the pipelines' table.
- */
-export default {
- props: {
- pipeline: {
- type: Object,
- required: true,
+ /**
+ * Pipeline table row.
+ *
+ * Given the received object renders a table row in the pipelines' table.
+ */
+ export default {
+ components: {
+ asyncButtonComponent,
+ pipelinesActionsComponent,
+ pipelinesArtifactsComponent,
+ commitComponent,
+ pipelineStage,
+ pipelineUrl,
+ ciBadge,
+ pipelinesTimeago,
},
- updateGraphDropdown: {
- type: Boolean,
- required: false,
- default: false,
+ props: {
+ pipeline: {
+ type: Object,
+ required: true,
+ },
+ updateGraphDropdown: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ autoDevopsHelpPath: {
+ type: String,
+ required: true,
+ },
+ viewType: {
+ type: String,
+ required: true,
+ },
},
- autoDevopsHelpPath: {
- type: String,
- required: true,
- },
- viewType: {
- type: String,
- required: true,
- },
- },
- components: {
- asyncButtonComponent,
- pipelinesActionsComponent,
- pipelinesArtifactsComponent,
- commitComponent,
- pipelineStage,
- pipelineUrl,
- ciBadge,
- pipelinesTimeago,
- },
- computed: {
- /**
- * If provided, returns the commit tag.
- * Needed to render the commit component column.
- *
- * This field needs a lot of verification, because of different possible cases:
- *
- * 1. person who is an author of a commit might be a GitLab user
- * 2. if person who is an author of a commit is a GitLab user he/she can have a GitLab avatar
- * 3. If GitLab user does not have avatar he/she might have a Gravatar
- * 4. If committer is not a GitLab User he/she can have a Gravatar
- * 5. We do not have consistent API object in this case
- * 6. We should improve API and the code
- *
- * @returns {Object|Undefined}
- */
- commitAuthor() {
- let commitAuthorInformation;
+ computed: {
+ /**
+ * If provided, returns the commit tag.
+ * Needed to render the commit component column.
+ *
+ * This field needs a lot of verification, because of different possible cases:
+ *
+ * 1. person who is an author of a commit might be a GitLab user
+ * 2. if person who is an author of a commit is a GitLab user he/she can have a GitLab avatar
+ * 3. If GitLab user does not have avatar he/she might have a Gravatar
+ * 4. If committer is not a GitLab User he/she can have a Gravatar
+ * 5. We do not have consistent API object in this case
+ * 6. We should improve API and the code
+ *
+ * @returns {Object|Undefined}
+ */
+ commitAuthor() {
+ let commitAuthorInformation;
- if (!this.pipeline || !this.pipeline.commit) {
- return null;
- }
+ if (!this.pipeline || !this.pipeline.commit) {
+ return null;
+ }
- // 1. person who is an author of a commit might be a GitLab user
- if (this.pipeline.commit.author) {
- // 2. if person who is an author of a commit is a GitLab user
- // he/she can have a GitLab avatar
- if (this.pipeline.commit.author.avatar_url) {
- commitAuthorInformation = this.pipeline.commit.author;
+ // 1. person who is an author of a commit might be a GitLab user
+ if (this.pipeline.commit.author) {
+ // 2. if person who is an author of a commit is a GitLab user
+ // he/she can have a GitLab avatar
+ if (this.pipeline.commit.author.avatar_url) {
+ commitAuthorInformation = this.pipeline.commit.author;
- // 3. If GitLab user does not have avatar he/she might have a Gravatar
- } else if (this.pipeline.commit.author_gravatar_url) {
- commitAuthorInformation = Object.assign({}, this.pipeline.commit.author, {
+ // 3. If GitLab user does not have avatar he/she might have a Gravatar
+ } else if (this.pipeline.commit.author_gravatar_url) {
+ commitAuthorInformation = Object.assign({}, this.pipeline.commit.author, {
+ avatar_url: this.pipeline.commit.author_gravatar_url,
+ });
+ }
+ // 4. If committer is not a GitLab User he/she can have a Gravatar
+ } else {
+ commitAuthorInformation = {
avatar_url: this.pipeline.commit.author_gravatar_url,
- });
+ path: `mailto:${this.pipeline.commit.author_email}`,
+ username: this.pipeline.commit.author_name,
+ };
}
- // 4. If committer is not a GitLab User he/she can have a Gravatar
- } else {
- commitAuthorInformation = {
- avatar_url: this.pipeline.commit.author_gravatar_url,
- path: `mailto:${this.pipeline.commit.author_email}`,
- username: this.pipeline.commit.author_name,
- };
- }
- return commitAuthorInformation;
- },
+ return commitAuthorInformation;
+ },
- /**
- * If provided, returns the commit tag.
- * Needed to render the commit component column.
- *
- * @returns {String|Undefined}
- */
- commitTag() {
- if (this.pipeline.ref &&
- this.pipeline.ref.tag) {
- return this.pipeline.ref.tag;
- }
- return undefined;
- },
+ /**
+ * If provided, returns the commit tag.
+ * Needed to render the commit component column.
+ *
+ * @returns {String|Undefined}
+ */
+ commitTag() {
+ if (this.pipeline.ref &&
+ this.pipeline.ref.tag) {
+ return this.pipeline.ref.tag;
+ }
+ return undefined;
+ },
- /**
- * If provided, returns the commit ref.
- * Needed to render the commit component column.
- *
- * Matches `path` prop sent in the API to `ref_url` prop needed
- * in the commit component.
- *
- * @returns {Object|Undefined}
- */
- commitRef() {
- if (this.pipeline.ref) {
- return Object.keys(this.pipeline.ref).reduce((accumulator, prop) => {
- if (prop === 'path') {
- accumulator.ref_url = this.pipeline.ref[prop];
- } else {
- accumulator[prop] = this.pipeline.ref[prop];
- }
- return accumulator;
- }, {});
- }
+ /**
+ * If provided, returns the commit ref.
+ * Needed to render the commit component column.
+ *
+ * Matches `path` prop sent in the API to `ref_url` prop needed
+ * in the commit component.
+ *
+ * @returns {Object|Undefined}
+ */
+ commitRef() {
+ if (this.pipeline.ref) {
+ return Object.keys(this.pipeline.ref).reduce((accumulator, prop) => {
+ if (prop === 'path') {
+ accumulator.ref_url = this.pipeline.ref[prop];
+ } else {
+ accumulator[prop] = this.pipeline.ref[prop];
+ }
+ return accumulator;
+ }, {});
+ }
- return undefined;
- },
+ return undefined;
+ },
- /**
- * If provided, returns the commit url.
- * Needed to render the commit component column.
- *
- * @returns {String|Undefined}
- */
- commitUrl() {
- if (this.pipeline.commit &&
- this.pipeline.commit.commit_path) {
- return this.pipeline.commit.commit_path;
- }
- return undefined;
- },
+ /**
+ * If provided, returns the commit url.
+ * Needed to render the commit component column.
+ *
+ * @returns {String|Undefined}
+ */
+ commitUrl() {
+ if (this.pipeline.commit &&
+ this.pipeline.commit.commit_path) {
+ return this.pipeline.commit.commit_path;
+ }
+ return undefined;
+ },
- /**
- * If provided, returns the commit short sha.
- * Needed to render the commit component column.
- *
- * @returns {String|Undefined}
- */
- commitShortSha() {
- if (this.pipeline.commit &&
- this.pipeline.commit.short_id) {
- return this.pipeline.commit.short_id;
- }
- return undefined;
- },
+ /**
+ * If provided, returns the commit short sha.
+ * Needed to render the commit component column.
+ *
+ * @returns {String|Undefined}
+ */
+ commitShortSha() {
+ if (this.pipeline.commit &&
+ this.pipeline.commit.short_id) {
+ return this.pipeline.commit.short_id;
+ }
+ return undefined;
+ },
- /**
- * If provided, returns the commit title.
- * Needed to render the commit component column.
- *
- * @returns {String|Undefined}
- */
- commitTitle() {
- if (this.pipeline.commit &&
- this.pipeline.commit.title) {
- return this.pipeline.commit.title;
- }
- return undefined;
- },
+ /**
+ * If provided, returns the commit title.
+ * Needed to render the commit component column.
+ *
+ * @returns {String|Undefined}
+ */
+ commitTitle() {
+ if (this.pipeline.commit &&
+ this.pipeline.commit.title) {
+ return this.pipeline.commit.title;
+ }
+ return undefined;
+ },
- /**
- * Timeago components expects a number
- *
- * @return {type} description
- */
- pipelineDuration() {
- if (this.pipeline.details && this.pipeline.details.duration) {
- return this.pipeline.details.duration;
- }
+ /**
+ * Timeago components expects a number
+ *
+ * @return {type} description
+ */
+ pipelineDuration() {
+ if (this.pipeline.details && this.pipeline.details.duration) {
+ return this.pipeline.details.duration;
+ }
- return 0;
- },
+ return 0;
+ },
- /**
- * Timeago component expects a String.
- *
- * @return {String}
- */
- pipelineFinishedAt() {
- if (this.pipeline.details && this.pipeline.details.finished_at) {
- return this.pipeline.details.finished_at;
- }
+ /**
+ * Timeago component expects a String.
+ *
+ * @return {String}
+ */
+ pipelineFinishedAt() {
+ if (this.pipeline.details && this.pipeline.details.finished_at) {
+ return this.pipeline.details.finished_at;
+ }
- return '';
- },
+ return '';
+ },
- pipelineStatus() {
- if (this.pipeline.details && this.pipeline.details.status) {
- return this.pipeline.details.status;
- }
- return {};
- },
+ pipelineStatus() {
+ if (this.pipeline.details && this.pipeline.details.status) {
+ return this.pipeline.details.status;
+ }
+ return {};
+ },
- displayPipelineActions() {
- return this.pipeline.flags.retryable ||
- this.pipeline.flags.cancelable ||
- this.pipeline.details.manual_actions.length ||
- this.pipeline.details.artifacts.length;
- },
+ displayPipelineActions() {
+ return this.pipeline.flags.retryable ||
+ this.pipeline.flags.cancelable ||
+ this.pipeline.details.manual_actions.length ||
+ this.pipeline.details.artifacts.length;
+ },
- isChildView() {
- return this.viewType === 'child';
+ isChildView() {
+ return this.viewType === 'child';
+ },
},
- },
-};
+ };
</script>
<template>
<div class="commit gl-responsive-table-row">
<div class="table-section section-10 commit-link">
- <div class="table-mobile-header"
+ <div
+ class="table-mobile-header"
role="rowheader">
Status
</div>
@@ -229,14 +230,14 @@ export default {
<ci-badge
:status="pipelineStatus"
:show-text="!isChildView"
- />
+ />
</div>
</div>
<pipeline-url
:pipeline="pipeline"
:auto-devops-help-path="autoDevopsHelpPath"
- />
+ />
<div class="table-section section-25">
<div
@@ -253,7 +254,7 @@ export default {
:title="commitTitle"
:author="commitAuthor"
:show-branch="!isChildView"
- />
+ />
</div>
</div>
@@ -264,21 +265,24 @@ export default {
Stages
</div>
<div class="table-mobile-content">
- <div class="stage-container dropdown js-mini-pipeline-graph"
- v-if="pipeline.details.stages.length > 0"
- v-for="stage in pipeline.details.stages">
- <pipeline-stage
- :stage="stage"
- :update-dropdown="updateGraphDropdown"
+ <template v-if="pipeline.details.stages.length > 0">
+ <div
+ class="stage-container dropdown js-mini-pipeline-graph"
+ v-for="(stage, index) in pipeline.details.stages"
+ :key="index">
+ <pipeline-stage
+ :stage="stage"
+ :update-dropdown="updateGraphDropdown"
/>
- </div>
+ </div>
+ </template>
</div>
</div>
<pipelines-timeago
:duration="pipelineDuration"
:finished-time="pipelineFinishedAt"
- />
+ />
<div
v-if="displayPipelineActions"
@@ -287,13 +291,13 @@ export default {
<pipelines-actions-component
v-if="pipeline.details.manual_actions.length"
:actions="pipeline.details.manual_actions"
- />
+ />
<pipelines-artifacts-component
v-if="pipeline.details.artifacts.length"
class="hidden-xs hidden-sm"
:artifacts="pipeline.details.artifacts"
- />
+ />
<async-button-component
v-if="pipeline.flags.retryable"
@@ -301,7 +305,7 @@ export default {
css-class="js-pipelines-retry-button btn-default btn-retry"
title="Retry"
icon="repeat"
- />
+ />
<async-button-component
v-if="pipeline.flags.cancelable"
@@ -310,7 +314,7 @@ export default {
title="Cancel"
icon="remove"
confirm-action-message="Are you sure you want to cancel this pipeline?"
- />
+ />
</div>
</div>
</div>
diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/stage.vue
index ac9d9c901ca..58806aa114a 100644
--- a/app/assets/javascripts/pipelines/components/stage.vue
+++ b/app/assets/javascripts/pipelines/components/stage.vue
@@ -1,133 +1,135 @@
<script>
-/**
- * Renders each stage of the pipeline mini graph.
- *
- * Given the provided endpoint will make a request to
- * fetch the dropdown data when the stage is clicked.
- *
- * Request is made inside this component to make it reusable between:
- * 1. Pipelines main table
- * 2. Pipelines table in commit and Merge request views
- * 3. Merge request widget
- * 4. Commit widget
- */
-
-import Flash from '../../flash';
-import icon from '../../vue_shared/components/icon.vue';
-import loadingIcon from '../../vue_shared/components/loading_icon.vue';
-import tooltip from '../../vue_shared/directives/tooltip';
-
-export default {
- props: {
- stage: {
- type: Object,
- required: true,
+ /**
+ * Renders each stage of the pipeline mini graph.
+ *
+ * Given the provided endpoint will make a request to
+ * fetch the dropdown data when the stage is clicked.
+ *
+ * Request is made inside this component to make it reusable between:
+ * 1. Pipelines main table
+ * 2. Pipelines table in commit and Merge request views
+ * 3. Merge request widget
+ * 4. Commit widget
+ */
+
+ import Flash from '../../flash';
+ import icon from '../../vue_shared/components/icon.vue';
+ import loadingIcon from '../../vue_shared/components/loading_icon.vue';
+ import tooltip from '../../vue_shared/directives/tooltip';
+
+ export default {
+ components: {
+ loadingIcon,
+ icon,
},
- updateDropdown: {
- type: Boolean,
- required: false,
- default: false,
+ directives: {
+ tooltip,
},
- },
-
- directives: {
- tooltip,
- },
-
- data() {
- return {
- isLoading: false,
- dropdownContent: '',
- };
- },
-
- components: {
- loadingIcon,
- icon,
- },
-
- updated() {
- if (this.dropdownContent.length > 0) {
- this.stopDropdownClickPropagation();
- }
- },
-
- watch: {
- updateDropdown() {
- if (this.updateDropdown &&
- this.isDropdownOpen() &&
- !this.isLoading) {
- this.fetchJobs();
- }
- },
- },
- methods: {
- onClickStage() {
- if (!this.isDropdownOpen()) {
- this.isLoading = true;
- this.fetchJobs();
- }
+ props: {
+ stage: {
+ type: Object,
+ required: true,
+ },
+
+ updateDropdown: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
- fetchJobs() {
- this.$http.get(this.stage.dropdown_path)
- .then(response => response.json())
- .then((data) => {
- this.dropdownContent = data.html;
- this.isLoading = false;
- })
- .catch(() => {
- this.closeDropdown();
- this.isLoading = false;
-
- const flash = new Flash('Something went wrong on our end.');
- return flash;
- });
+ data() {
+ return {
+ isLoading: false,
+ dropdownContent: '',
+ };
},
- /**
- * When the user right clicks or cmd/ctrl + click in the job name
- * the dropdown should not be closed and the link should open in another tab,
- * so we stop propagation of the click event inside the dropdown.
- *
- * Since this component is rendered multiple times per page we need to guarantee we only
- * target the click event of this component.
- */
- stopDropdownClickPropagation() {
- $(this.$el.querySelectorAll('.js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item'))
- .on('click', (e) => {
- e.stopPropagation();
- });
- },
+ computed: {
+ dropdownClass() {
+ return this.dropdownContent.length > 0 ?
+ 'js-builds-dropdown-container' :
+ 'js-builds-dropdown-loading';
+ },
- closeDropdown() {
- if (this.isDropdownOpen()) {
- $(this.$refs.dropdown).dropdown('toggle');
- }
- },
+ triggerButtonClass() {
+ return `ci-status-icon-${this.stage.status.group}`;
+ },
- isDropdownOpen() {
- return this.$el.classList.contains('open');
+ borderlessIcon() {
+ return `${this.stage.status.icon}_borderless`;
+ },
},
- },
- computed: {
- dropdownClass() {
- return this.dropdownContent.length > 0 ? 'js-builds-dropdown-container' : 'js-builds-dropdown-loading';
+ watch: {
+ updateDropdown() {
+ if (this.updateDropdown &&
+ this.isDropdownOpen() &&
+ !this.isLoading) {
+ this.fetchJobs();
+ }
+ },
},
- triggerButtonClass() {
- return `ci-status-icon-${this.stage.status.group}`;
+ updated() {
+ if (this.dropdownContent.length > 0) {
+ this.stopDropdownClickPropagation();
+ }
},
- borderlessIcon() {
- return `${this.stage.status.icon}_borderless`;
+ methods: {
+ onClickStage() {
+ if (!this.isDropdownOpen()) {
+ this.isLoading = true;
+ this.fetchJobs();
+ }
+ },
+
+ fetchJobs() {
+ this.$http.get(this.stage.dropdown_path)
+ .then(response => response.json())
+ .then((data) => {
+ this.dropdownContent = data.html;
+ this.isLoading = false;
+ })
+ .catch(() => {
+ this.closeDropdown();
+ this.isLoading = false;
+
+ const flash = new Flash('Something went wrong on our end.');
+ return flash;
+ });
+ },
+
+ /**
+ * When the user right clicks or cmd/ctrl + click in the job name
+ * the dropdown should not be closed and the link should open in another tab,
+ * so we stop propagation of the click event inside the dropdown.
+ *
+ * Since this component is rendered multiple times per page we need to guarantee we only
+ * target the click event of this component.
+ */
+ stopDropdownClickPropagation() {
+ $(this.$el.querySelectorAll('.js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item'))
+ .on('click', (e) => {
+ e.stopPropagation();
+ });
+ },
+
+ closeDropdown() {
+ if (this.isDropdownOpen()) {
+ $(this.$refs.dropdown).dropdown('toggle');
+ }
+ },
+
+ isDropdownOpen() {
+ return this.$el.classList.contains('open');
+ },
},
- },
-};
+ };
</script>
<template>
@@ -143,36 +145,41 @@ export default {
type="button"
id="stageDropdown"
aria-haspopup="true"
- aria-expanded="false">
+ aria-expanded="false"
+ >
<span
aria-hidden="true"
- :aria-label="stage.title">
- <icon
- :name="borderlessIcon"/>
+ :aria-label="stage.title"
+ >
+ <icon :name="borderlessIcon" />
</span>
<i
class="fa fa-caret-down"
- aria-hidden="true">
+ aria-hidden="true"
+ >
</i>
</button>
<ul
class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container"
- aria-labelledby="stageDropdown">
+ aria-labelledby="stageDropdown"
+ >
<li
:class="dropdownClass"
- class="js-builds-dropdown-list scrollable-menu">
+ class="js-builds-dropdown-list scrollable-menu"
+ >
<loading-icon v-if="isLoading"/>
<ul
v-else
- v-html="dropdownContent">
+ v-html="dropdownContent"
+ >
</ul>
</li>
</ul>
</div>
-</script>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/time_ago.vue b/app/assets/javascripts/pipelines/components/time_ago.vue
index 037684b4e72..cd54d26c9d3 100644
--- a/app/assets/javascripts/pipelines/components/time_ago.vue
+++ b/app/assets/javascripts/pipelines/components/time_ago.vue
@@ -5,6 +5,12 @@
import timeagoMixin from '../../vue_shared/mixins/timeago';
export default {
+ directives: {
+ tooltip,
+ },
+ mixins: [
+ timeagoMixin,
+ ],
props: {
finishedTime: {
type: String,
@@ -15,12 +21,6 @@
required: true,
},
},
- mixins: [
- timeagoMixin,
- ],
- directives: {
- tooltip,
- },
data() {
return {
iconTimerSvg,
@@ -60,26 +60,29 @@
<div class="table-section section-15 pipelines-time-ago">
<div
class="table-mobile-header"
- role="rowheader">
+ role="rowheader"
+ >
Duration
</div>
<div class="table-mobile-content">
<p
class="duration"
- v-if="hasDuration">
- <span
- v-html="iconTimerSvg">
+ v-if="hasDuration"
+ >
+ <span v-html="iconTimerSvg">
</span>
- {{durationFormated}}
+ {{ durationFormated }}
</p>
<p
class="finished-at hidden-xs hidden-sm"
- v-if="hasFinishedTime">
+ v-if="hasFinishedTime"
+ >
<i
class="fa fa-calendar"
- aria-hidden="true">
+ aria-hidden="true"
+ >
</i>
<time
@@ -87,9 +90,9 @@
data-placement="top"
data-container="body"
:title="tooltipTitle(finishedTime)">
- {{timeFormated(finishedTime)}}
+ {{ timeFormated(finishedTime) }}
</time>
</p>
</div>
</div>
-</script>
+</template>
diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
index 206023d4ddb..d88d280cb3f 100644
--- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js
+++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
@@ -15,14 +15,14 @@ document.addEventListener('DOMContentLoaded', () => {
// eslint-disable-next-line
new Vue({
el: '#js-pipeline-graph-vue',
+ components: {
+ pipelineGraph,
+ },
data() {
return {
mediator,
};
},
- components: {
- pipelineGraph,
- },
render(createElement) {
return createElement('pipeline-graph', {
props: {
@@ -36,14 +36,14 @@ document.addEventListener('DOMContentLoaded', () => {
// eslint-disable-next-line
new Vue({
el: '#js-pipeline-header-vue',
+ components: {
+ pipelineHeader,
+ },
data() {
return {
mediator,
};
},
- components: {
- pipelineHeader,
- },
created() {
eventHub.$on('headerPostAction', this.postAction);
},
diff --git a/app/assets/javascripts/pipelines/pipelines_bundle.js b/app/assets/javascripts/pipelines/pipelines_bundle.js
index 3e4b6eeb5bf..ab5596e70f0 100644
--- a/app/assets/javascripts/pipelines/pipelines_bundle.js
+++ b/app/assets/javascripts/pipelines/pipelines_bundle.js
@@ -7,6 +7,9 @@ Vue.use(Translate);
document.addEventListener('DOMContentLoaded', () => new Vue({
el: '#pipelines-list-vue',
+ components: {
+ pipelinesComponent,
+ },
data() {
const store = new PipelinesStore();
@@ -14,9 +17,6 @@ document.addEventListener('DOMContentLoaded', () => new Vue({
store,
};
},
- components: {
- pipelinesComponent,
- },
render(createElement) {
return createElement('pipelines-component', {
props: {
diff --git a/app/assets/javascripts/profile/account/components/delete_account_modal.vue b/app/assets/javascripts/profile/account/components/delete_account_modal.vue
index 36ad618aa46..1ffe482d782 100644
--- a/app/assets/javascripts/profile/account/components/delete_account_modal.vue
+++ b/app/assets/javascripts/profile/account/components/delete_account_modal.vue
@@ -4,6 +4,9 @@
import csrf from '~/lib/utils/csrf';
export default {
+ components: {
+ modal,
+ },
props: {
actionUrl: {
type: String,
@@ -24,9 +27,6 @@
enteredUsername: '',
};
},
- components: {
- modal,
- },
computed: {
csrfToken() {
return csrf.token;
@@ -85,7 +85,9 @@ Once you confirm %{deleteAccount}, it cannot be undone or recovered.`),
@submit="onSubmit"
:submit-disabled="!canSubmit()">
- <template slot="body" slot-scope="props">
+ <template
+ slot="body"
+ slot-scope="props">
<p v-html="props.text"></p>
<form
@@ -96,13 +98,19 @@ Once you confirm %{deleteAccount}, it cannot be undone or recovered.`),
<input
type="hidden"
name="_method"
- value="delete" />
+ value="delete"
+ />
<input
type="hidden"
name="authenticity_token"
- :value="csrfToken" />
+ :value="csrfToken"
+ />
- <p id="input-label" v-html="inputLabel"></p>
+ <p
+ id="input-label"
+ v-html="inputLabel"
+ >
+ </p>
<input
v-if="confirmWithPassword"
@@ -110,14 +118,16 @@ Once you confirm %{deleteAccount}, it cannot be undone or recovered.`),
class="form-control"
type="password"
v-model="enteredPassword"
- aria-labelledby="input-label" />
+ aria-labelledby="input-label"
+ />
<input
v-else
name="username"
class="form-control"
type="text"
v-model="enteredUsername"
- aria-labelledby="input-label" />
+ aria-labelledby="input-label"
+ />
</form>
</template>
diff --git a/app/assets/javascripts/projects/permissions/components/project_feature_setting.vue b/app/assets/javascripts/projects/permissions/components/project_feature_setting.vue
index 8fce4c63872..3ebfe82597a 100644
--- a/app/assets/javascripts/projects/permissions/components/project_feature_setting.vue
+++ b/app/assets/javascripts/projects/permissions/components/project_feature_setting.vue
@@ -1,77 +1,80 @@
<script>
-import projectFeatureToggle from '../../../vue_shared/components/toggle_button.vue';
+ import projectFeatureToggle from '../../../vue_shared/components/toggle_button.vue';
-export default {
- props: {
- name: {
- type: String,
- required: false,
- default: '',
+ export default {
+ components: {
+ projectFeatureToggle,
},
- options: {
- type: Array,
- required: false,
- default: () => [],
- },
- value: {
- type: Number,
- required: false,
- default: 0,
- },
- disabledInput: {
- type: Boolean,
- required: false,
- default: false,
- },
- },
-
- components: {
- projectFeatureToggle,
- },
- computed: {
- featureEnabled() {
- return this.value !== 0;
+ model: {
+ prop: 'value',
+ event: 'change',
},
- displayOptions() {
- if (this.featureEnabled) {
- return this.options;
- }
- return [
- [0, 'Enable feature to choose access level'],
- ];
+ props: {
+ name: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ options: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ value: {
+ type: Number,
+ required: false,
+ default: 0,
+ },
+ disabledInput: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
- displaySelectInput() {
- return this.disabledInput || !this.featureEnabled || this.displayOptions.length < 2;
- },
- },
+ computed: {
+ featureEnabled() {
+ return this.value !== 0;
+ },
- model: {
- prop: 'value',
- event: 'change',
- },
+ displayOptions() {
+ if (this.featureEnabled) {
+ return this.options;
+ }
+ return [
+ [0, 'Enable feature to choose access level'],
+ ];
+ },
- methods: {
- toggleFeature(featureEnabled) {
- if (featureEnabled === false || this.options.length < 1) {
- this.$emit('change', 0);
- } else {
- const [firstOptionValue] = this.options[this.options.length - 1];
- this.$emit('change', firstOptionValue);
- }
+ displaySelectInput() {
+ return this.disabledInput || !this.featureEnabled || this.displayOptions.length < 2;
+ },
},
- selectOption(e) {
- this.$emit('change', Number(e.target.value));
+ methods: {
+ toggleFeature(featureEnabled) {
+ if (featureEnabled === false || this.options.length < 1) {
+ this.$emit('change', 0);
+ } else {
+ const [firstOptionValue] = this.options[this.options.length - 1];
+ this.$emit('change', firstOptionValue);
+ }
+ },
+
+ selectOption(e) {
+ this.$emit('change', Number(e.target.value));
+ },
},
- },
-};
+ };
</script>
<template>
- <div class="project-feature-controls" :data-for="name">
+ <div
+ class="project-feature-controls"
+ :data-for="name"
+ >
<input
v-if="name"
type="hidden"
@@ -81,7 +84,7 @@ export default {
<project-feature-toggle
:value="featureEnabled"
@change="toggleFeature"
- :disabledInput="disabledInput"
+ :disabled-input="disabledInput"
/>
<div class="select-wrapper">
<select
@@ -95,10 +98,14 @@ export default {
:value="optionValue"
:selected="optionValue === value"
>
- {{optionName}}
+ {{ optionName }}
</option>
</select>
- <i aria-hidden="true" class="fa fa-chevron-down"></i>
+ <i
+ aria-hidden="true"
+ class="fa fa-chevron-down"
+ >
+ </i>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/projects/permissions/components/project_setting_row.vue b/app/assets/javascripts/projects/permissions/components/project_setting_row.vue
index 6140d74fea8..25a88f846eb 100644
--- a/app/assets/javascripts/projects/permissions/components/project_setting_row.vue
+++ b/app/assets/javascripts/projects/permissions/components/project_setting_row.vue
@@ -1,36 +1,51 @@
<script>
-export default {
- props: {
- label: {
- type: String,
- required: false,
- default: null,
+ export default {
+ props: {
+ label: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ helpPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ helpText: {
+ type: String,
+ required: false,
+ default: null,
+ },
},
- helpPath: {
- type: String,
- required: false,
- default: null,
- },
- helpText: {
- type: String,
- required: false,
- default: null,
- },
- },
-};
+ };
</script>
<template>
<div class="project-feature-row">
- <label v-if="label" class="label-light">
- {{label}}
- <a v-if="helpPath" :href="helpPath" target="_blank">
- <i aria-hidden="true" data-hidden="true" class="fa fa-question-circle"></i>
+ <label
+ v-if="label"
+ class="label-light"
+ >
+ {{ label }}
+ <a
+ v-if="helpPath"
+ :href="helpPath"
+ target="_blank"
+ >
+ <i
+ aria-hidden="true"
+ data-hidden="true"
+ class="fa fa-question-circle"
+ >
+ </i>
</a>
</label>
- <span v-if="helpText" class="help-block">
- {{helpText}}
+ <span
+ v-if="helpText"
+ class="help-block"
+ >
+ {{ helpText }}
</span>
- <slot />
+ <slot></slot>
</div>
</template>
diff --git a/app/assets/javascripts/projects/permissions/components/settings_panel.vue b/app/assets/javascripts/projects/permissions/components/settings_panel.vue
index 639429baf26..c96ce12d9fb 100644
--- a/app/assets/javascripts/projects/permissions/components/settings_panel.vue
+++ b/app/assets/javascripts/projects/permissions/components/settings_panel.vue
@@ -1,172 +1,174 @@
<script>
-import projectFeatureSetting from './project_feature_setting.vue';
-import projectFeatureToggle from '../../../vue_shared/components/toggle_button.vue';
-import projectSettingRow from './project_setting_row.vue';
-import { visibilityOptions, visibilityLevelDescriptions } from '../constants';
-import { toggleHiddenClassBySelector } from '../external';
+ import projectFeatureSetting from './project_feature_setting.vue';
+ import projectFeatureToggle from '../../../vue_shared/components/toggle_button.vue';
+ import projectSettingRow from './project_setting_row.vue';
+ import { visibilityOptions, visibilityLevelDescriptions } from '../constants';
+ import { toggleHiddenClassBySelector } from '../external';
-export default {
- props: {
- currentSettings: {
- type: Object,
- required: true,
+ export default {
+ components: {
+ projectFeatureSetting,
+ projectFeatureToggle,
+ projectSettingRow,
},
- canChangeVisibilityLevel: {
- type: Boolean,
- required: false,
- default: false,
- },
- allowedVisibilityOptions: {
- type: Array,
- required: false,
- default: () => [0, 10, 20],
- },
- lfsAvailable: {
- type: Boolean,
- required: false,
- default: false,
- },
- registryAvailable: {
- type: Boolean,
- required: false,
- default: false,
- },
- visibilityHelpPath: {
- type: String,
- required: false,
- },
- lfsHelpPath: {
- type: String,
- required: false,
- },
- registryHelpPath: {
- type: String,
- required: false,
- },
- },
- data() {
- const defaults = {
- visibilityOptions,
- visibilityLevel: visibilityOptions.PUBLIC,
- issuesAccessLevel: 20,
- repositoryAccessLevel: 20,
- mergeRequestsAccessLevel: 20,
- buildsAccessLevel: 20,
- wikiAccessLevel: 20,
- snippetsAccessLevel: 20,
- containerRegistryEnabled: true,
- lfsEnabled: true,
- requestAccessEnabled: true,
- highlightChangesClass: false,
- };
-
- return { ...defaults, ...this.currentSettings };
- },
-
- components: {
- projectFeatureSetting,
- projectFeatureToggle,
- projectSettingRow,
- },
-
- computed: {
- featureAccessLevelOptions() {
- const options = [
- [10, 'Only Project Members'],
- ];
- if (this.visibilityLevel !== visibilityOptions.PRIVATE) {
- options.push([20, 'Everyone With Access']);
- }
- return options;
+ props: {
+ currentSettings: {
+ type: Object,
+ required: true,
+ },
+ canChangeVisibilityLevel: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ allowedVisibilityOptions: {
+ type: Array,
+ required: false,
+ default: () => [0, 10, 20],
+ },
+ lfsAvailable: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ registryAvailable: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ visibilityHelpPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ lfsHelpPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ registryHelpPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
- repoFeatureAccessLevelOptions() {
- return this.featureAccessLevelOptions.filter(
- ([value]) => value <= this.repositoryAccessLevel,
- );
- },
+ data() {
+ const defaults = {
+ visibilityOptions,
+ visibilityLevel: visibilityOptions.PUBLIC,
+ issuesAccessLevel: 20,
+ repositoryAccessLevel: 20,
+ mergeRequestsAccessLevel: 20,
+ buildsAccessLevel: 20,
+ wikiAccessLevel: 20,
+ snippetsAccessLevel: 20,
+ containerRegistryEnabled: true,
+ lfsEnabled: true,
+ requestAccessEnabled: true,
+ highlightChangesClass: false,
+ };
- repositoryEnabled() {
- return this.repositoryAccessLevel > 0;
+ return { ...defaults, ...this.currentSettings };
},
- visibilityLevelDescription() {
- return visibilityLevelDescriptions[this.visibilityLevel];
- },
- },
+ computed: {
+ featureAccessLevelOptions() {
+ const options = [
+ [10, 'Only Project Members'],
+ ];
+ if (this.visibilityLevel !== visibilityOptions.PRIVATE) {
+ options.push([20, 'Everyone With Access']);
+ }
+ return options;
+ },
- methods: {
- highlightChanges() {
- this.highlightChangesClass = true;
- this.$nextTick(() => {
- this.highlightChangesClass = false;
- });
- },
+ repoFeatureAccessLevelOptions() {
+ return this.featureAccessLevelOptions.filter(
+ ([value]) => value <= this.repositoryAccessLevel,
+ );
+ },
- visibilityAllowed(option) {
- return this.allowedVisibilityOptions.includes(option);
- },
- },
+ repositoryEnabled() {
+ return this.repositoryAccessLevel > 0;
+ },
- watch: {
- visibilityLevel(value, oldValue) {
- if (value === visibilityOptions.PRIVATE) {
- // when private, features are restricted to "only team members"
- this.issuesAccessLevel = Math.min(10, this.issuesAccessLevel);
- this.repositoryAccessLevel = Math.min(10, this.repositoryAccessLevel);
- this.mergeRequestsAccessLevel = Math.min(10, this.mergeRequestsAccessLevel);
- this.buildsAccessLevel = Math.min(10, this.buildsAccessLevel);
- this.wikiAccessLevel = Math.min(10, this.wikiAccessLevel);
- this.snippetsAccessLevel = Math.min(10, this.snippetsAccessLevel);
- this.highlightChanges();
- } else if (oldValue === visibilityOptions.PRIVATE) {
- // if changing away from private, make enabled features more permissive
- if (this.issuesAccessLevel > 0) this.issuesAccessLevel = 20;
- if (this.repositoryAccessLevel > 0) this.repositoryAccessLevel = 20;
- if (this.mergeRequestsAccessLevel > 0) this.mergeRequestsAccessLevel = 20;
- if (this.buildsAccessLevel > 0) this.buildsAccessLevel = 20;
- if (this.wikiAccessLevel > 0) this.wikiAccessLevel = 20;
- if (this.snippetsAccessLevel > 0) this.snippetsAccessLevel = 20;
- this.highlightChanges();
- }
+ visibilityLevelDescription() {
+ return visibilityLevelDescriptions[this.visibilityLevel];
+ },
},
- repositoryAccessLevel(value, oldValue) {
- if (value < oldValue) {
- // sub-features cannot have more premissive access level
- this.mergeRequestsAccessLevel = Math.min(this.mergeRequestsAccessLevel, value);
- this.buildsAccessLevel = Math.min(this.buildsAccessLevel, value);
+ watch: {
+ visibilityLevel(value, oldValue) {
+ if (value === visibilityOptions.PRIVATE) {
+ // when private, features are restricted to "only team members"
+ this.issuesAccessLevel = Math.min(10, this.issuesAccessLevel);
+ this.repositoryAccessLevel = Math.min(10, this.repositoryAccessLevel);
+ this.mergeRequestsAccessLevel = Math.min(10, this.mergeRequestsAccessLevel);
+ this.buildsAccessLevel = Math.min(10, this.buildsAccessLevel);
+ this.wikiAccessLevel = Math.min(10, this.wikiAccessLevel);
+ this.snippetsAccessLevel = Math.min(10, this.snippetsAccessLevel);
+ this.highlightChanges();
+ } else if (oldValue === visibilityOptions.PRIVATE) {
+ // if changing away from private, make enabled features more permissive
+ if (this.issuesAccessLevel > 0) this.issuesAccessLevel = 20;
+ if (this.repositoryAccessLevel > 0) this.repositoryAccessLevel = 20;
+ if (this.mergeRequestsAccessLevel > 0) this.mergeRequestsAccessLevel = 20;
+ if (this.buildsAccessLevel > 0) this.buildsAccessLevel = 20;
+ if (this.wikiAccessLevel > 0) this.wikiAccessLevel = 20;
+ if (this.snippetsAccessLevel > 0) this.snippetsAccessLevel = 20;
+ this.highlightChanges();
+ }
+ },
+
+ repositoryAccessLevel(value, oldValue) {
+ if (value < oldValue) {
+ // sub-features cannot have more premissive access level
+ this.mergeRequestsAccessLevel = Math.min(this.mergeRequestsAccessLevel, value);
+ this.buildsAccessLevel = Math.min(this.buildsAccessLevel, value);
- if (value === 0) {
- this.containerRegistryEnabled = false;
- this.lfsEnabled = false;
+ if (value === 0) {
+ this.containerRegistryEnabled = false;
+ this.lfsEnabled = false;
+ }
+ } else if (oldValue === 0) {
+ this.mergeRequestsAccessLevel = value;
+ this.buildsAccessLevel = value;
+ this.containerRegistryEnabled = true;
+ this.lfsEnabled = true;
}
- } else if (oldValue === 0) {
- this.mergeRequestsAccessLevel = value;
- this.buildsAccessLevel = value;
- this.containerRegistryEnabled = true;
- this.lfsEnabled = true;
- }
- },
+ },
- issuesAccessLevel(value, oldValue) {
- if (value === 0) toggleHiddenClassBySelector('.issues-feature', true);
- else if (oldValue === 0) toggleHiddenClassBySelector('.issues-feature', false);
- },
+ issuesAccessLevel(value, oldValue) {
+ if (value === 0) toggleHiddenClassBySelector('.issues-feature', true);
+ else if (oldValue === 0) toggleHiddenClassBySelector('.issues-feature', false);
+ },
- mergeRequestsAccessLevel(value, oldValue) {
- if (value === 0) toggleHiddenClassBySelector('.merge-requests-feature', true);
- else if (oldValue === 0) toggleHiddenClassBySelector('.merge-requests-feature', false);
- },
+ mergeRequestsAccessLevel(value, oldValue) {
+ if (value === 0) toggleHiddenClassBySelector('.merge-requests-feature', true);
+ else if (oldValue === 0) toggleHiddenClassBySelector('.merge-requests-feature', false);
+ },
- buildsAccessLevel(value, oldValue) {
- if (value === 0) toggleHiddenClassBySelector('.builds-feature', true);
- else if (oldValue === 0) toggleHiddenClassBySelector('.builds-feature', false);
+ buildsAccessLevel(value, oldValue) {
+ if (value === 0) toggleHiddenClassBySelector('.builds-feature', true);
+ else if (oldValue === 0) toggleHiddenClassBySelector('.builds-feature', false);
+ },
},
- },
-};
+ methods: {
+ highlightChanges() {
+ this.highlightChangesClass = true;
+ this.$nextTick(() => {
+ this.highlightChangesClass = false;
+ });
+ },
+
+ visibilityAllowed(option) {
+ return this.allowedVisibilityOptions.includes(option);
+ },
+ },
+ };
</script>
<template>
@@ -203,22 +205,36 @@ export default {
Public
</option>
</select>
- <i aria-hidden="true" data-hidden="true" class="fa fa-chevron-down"></i>
+ <i
+ aria-hidden="true"
+ data-hidden="true"
+ class="fa fa-chevron-down"
+ >
+ </i>
</div>
</div>
<span class="help-block">{{ visibilityLevelDescription }}</span>
- <label v-if="visibilityLevel !== visibilityOptions.PUBLIC" class="request-access">
+ <label
+ v-if="visibilityLevel !== visibilityOptions.PUBLIC"
+ class="request-access"
+ >
<input
type="hidden"
name="project[request_access_enabled]"
:value="requestAccessEnabled"
/>
- <input type="checkbox" v-model="requestAccessEnabled" />
+ <input
+ type="checkbox"
+ v-model="requestAccessEnabled"
+ />
Allow users to request access
</label>
</project-setting-row>
</div>
- <div class="project-feature-settings" :class="{ 'highlight-changes': highlightChangesClass }">
+ <div
+ class="project-feature-settings"
+ :class="{ 'highlight-changes': highlightChangesClass }"
+ >
<project-setting-row
label="Issues"
help-text="Lightweight issue tracking system for this project"
@@ -248,7 +264,7 @@ export default {
name="project[project_feature_attributes][merge_requests_access_level]"
:options="repoFeatureAccessLevelOptions"
v-model="mergeRequestsAccessLevel"
- :disabledInput="!repositoryEnabled"
+ :disabled-input="!repositoryEnabled"
/>
</project-setting-row>
<project-setting-row
@@ -259,7 +275,7 @@ export default {
name="project[project_feature_attributes][builds_access_level]"
:options="repoFeatureAccessLevelOptions"
v-model="buildsAccessLevel"
- :disabledInput="!repositoryEnabled"
+ :disabled-input="!repositoryEnabled"
/>
</project-setting-row>
<project-setting-row
@@ -271,7 +287,7 @@ export default {
<project-feature-toggle
name="project[container_registry_enabled]"
v-model="containerRegistryEnabled"
- :disabledInput="!repositoryEnabled"
+ :disabled-input="!repositoryEnabled"
/>
</project-setting-row>
<project-setting-row
@@ -283,7 +299,7 @@ export default {
<project-feature-toggle
name="project[lfs_enabled]"
v-model="lfsEnabled"
- :disabledInput="!repositoryEnabled"
+ :disabled-input="!repositoryEnabled"
/>
</project-setting-row>
</div>
diff --git a/app/assets/javascripts/projects_dropdown/components/app.vue b/app/assets/javascripts/projects_dropdown/components/app.vue
index 7606605be32..34a60dd574b 100644
--- a/app/assets/javascripts/projects_dropdown/components/app.vue
+++ b/app/assets/javascripts/projects_dropdown/components/app.vue
@@ -47,6 +47,22 @@ export default {
return this.store.getSearchedProjects();
},
},
+ created() {
+ if (this.currentProject.id) {
+ this.logCurrentProjectAccess();
+ }
+
+ eventHub.$on('dropdownOpen', this.fetchFrequentProjects);
+ eventHub.$on('searchProjects', this.fetchSearchedProjects);
+ eventHub.$on('searchCleared', this.handleSearchClear);
+ eventHub.$on('searchFailed', this.handleSearchFailure);
+ },
+ beforeDestroy() {
+ eventHub.$off('dropdownOpen', this.fetchFrequentProjects);
+ eventHub.$off('searchProjects', this.fetchSearchedProjects);
+ eventHub.$off('searchCleared', this.handleSearchClear);
+ eventHub.$off('searchFailed', this.handleSearchFailure);
+ },
methods: {
toggleFrequentProjectsList(state) {
this.isLoadingProjects = !state;
@@ -108,22 +124,6 @@ export default {
this.toggleSearchProjectsList(true);
},
},
- created() {
- if (this.currentProject.id) {
- this.logCurrentProjectAccess();
- }
-
- eventHub.$on('dropdownOpen', this.fetchFrequentProjects);
- eventHub.$on('searchProjects', this.fetchSearchedProjects);
- eventHub.$on('searchCleared', this.handleSearchClear);
- eventHub.$on('searchFailed', this.handleSearchFailure);
- },
- beforeDestroy() {
- eventHub.$off('dropdownOpen', this.fetchFrequentProjects);
- eventHub.$off('searchProjects', this.fetchSearchedProjects);
- eventHub.$off('searchCleared', this.handleSearchClear);
- eventHub.$off('searchFailed', this.handleSearchFailure);
- },
};
</script>
diff --git a/app/assets/javascripts/projects_dropdown/components/projects_list_frequent.vue b/app/assets/javascripts/projects_dropdown/components/projects_list_frequent.vue
index 093554cd0bc..246dbeaaded 100644
--- a/app/assets/javascripts/projects_dropdown/components/projects_list_frequent.vue
+++ b/app/assets/javascripts/projects_dropdown/components/projects_list_frequent.vue
@@ -1,32 +1,32 @@
<script>
-import { s__ } from '../../locale';
-import projectsListItem from './projects_list_item.vue';
+ import { s__ } from '../../locale';
+ import projectsListItem from './projects_list_item.vue';
-export default {
- components: {
- projectsListItem,
- },
- props: {
- projects: {
- type: Array,
- required: true,
+ export default {
+ components: {
+ projectsListItem,
},
- localStorageFailed: {
- type: Boolean,
- required: true,
+ props: {
+ projects: {
+ type: Array,
+ required: true,
+ },
+ localStorageFailed: {
+ type: Boolean,
+ required: true,
+ },
},
- },
- computed: {
- isListEmpty() {
- return this.projects.length === 0;
+ computed: {
+ isListEmpty() {
+ return this.projects.length === 0;
+ },
+ listEmptyMessage() {
+ return this.localStorageFailed ?
+ s__('ProjectsDropdown|This feature requires browser localStorage support') :
+ s__('ProjectsDropdown|Projects you visit often will appear here');
+ },
},
- listEmptyMessage() {
- return this.localStorageFailed ?
- s__('ProjectsDropdown|This feature requires browser localStorage support') :
- s__('ProjectsDropdown|Projects you visit often will appear here');
- },
- },
-};
+ };
</script>
<template>
@@ -40,7 +40,7 @@ export default {
class="section-empty"
v-if="isListEmpty"
>
- {{listEmptyMessage}}
+ {{ listEmptyMessage }}
</li>
<projects-list-item
v-else
diff --git a/app/assets/javascripts/projects_dropdown/components/projects_list_item.vue b/app/assets/javascripts/projects_dropdown/components/projects_list_item.vue
index d482a7025de..759cdd1ded9 100644
--- a/app/assets/javascripts/projects_dropdown/components/projects_list_item.vue
+++ b/app/assets/javascripts/projects_dropdown/components/projects_list_item.vue
@@ -1,76 +1,77 @@
<script>
-import identicon from '../../vue_shared/components/identicon.vue';
+ /* eslint-disable vue/require-default-prop, vue/require-prop-types */
+ import identicon from '../../vue_shared/components/identicon.vue';
-export default {
- components: {
- identicon,
- },
- props: {
- matcher: {
- type: String,
- required: false,
+ export default {
+ components: {
+ identicon,
},
- projectId: {
- type: Number,
- required: true,
- },
- projectName: {
- type: String,
- required: true,
- },
- namespace: {
- type: String,
- required: true,
- },
- webUrl: {
- type: String,
- required: true,
- },
- avatarUrl: {
- required: true,
- validator(value) {
- return value === null || typeof value === 'string';
+ props: {
+ matcher: {
+ type: String,
+ required: false,
+ },
+ projectId: {
+ type: Number,
+ required: true,
+ },
+ projectName: {
+ type: String,
+ required: true,
+ },
+ namespace: {
+ type: String,
+ required: true,
+ },
+ webUrl: {
+ type: String,
+ required: true,
+ },
+ avatarUrl: {
+ required: true,
+ validator(value) {
+ return value === null || typeof value === 'string';
+ },
},
},
- },
- computed: {
- hasAvatar() {
- return this.avatarUrl !== null;
- },
- highlightedProjectName() {
- if (this.matcher) {
- const matcherRegEx = new RegExp(this.matcher, 'gi');
- const matches = this.projectName.match(matcherRegEx);
+ computed: {
+ hasAvatar() {
+ return this.avatarUrl !== null;
+ },
+ highlightedProjectName() {
+ if (this.matcher) {
+ const matcherRegEx = new RegExp(this.matcher, 'gi');
+ const matches = this.projectName.match(matcherRegEx);
- if (matches && matches.length > 0) {
- return this.projectName.replace(matches[0], `<b>${matches[0]}</b>`);
+ if (matches && matches.length > 0) {
+ return this.projectName.replace(matches[0], `<b>${matches[0]}</b>`);
+ }
}
- }
- return this.projectName;
- },
- /**
- * Smartly truncates project namespace by doing two things;
- * 1. Only include Group names in path by removing project name
- * 2. Only include first and last group names in the path
- * when namespace has more than 2 groups present
- *
- * First part (removal of project name from namespace) can be
- * done from backend but doing so involves migration of
- * existing project namespaces which is not wise thing to do.
- */
- truncatedNamespace() {
- const namespaceArr = this.namespace.split(' / ');
- namespaceArr.splice(-1, 1);
- let namespace = namespaceArr.join(' / ');
+ return this.projectName;
+ },
+ /**
+ * Smartly truncates project namespace by doing two things;
+ * 1. Only include Group names in path by removing project name
+ * 2. Only include first and last group names in the path
+ * when namespace has more than 2 groups present
+ *
+ * First part (removal of project name from namespace) can be
+ * done from backend but doing so involves migration of
+ * existing project namespaces which is not wise thing to do.
+ */
+ truncatedNamespace() {
+ const namespaceArr = this.namespace.split(' / ');
+ namespaceArr.splice(-1, 1);
+ let namespace = namespaceArr.join(' / ');
- if (namespaceArr.length > 2) {
- namespace = `${namespaceArr[0]} / ... / ${namespaceArr.pop()}`;
- }
+ if (namespaceArr.length > 2) {
+ namespace = `${namespaceArr[0]} / ... / ${namespaceArr.pop()}`;
+ }
- return namespace;
+ return namespace;
+ },
},
- },
-};
+ };
</script>
<template>
@@ -92,7 +93,7 @@ export default {
<identicon
v-else
size-class="s32"
- :entity-id=projectId
+ :entity-id="projectId"
:entity-name="projectName"
/>
</div>
@@ -108,7 +109,7 @@ export default {
<div
class="project-namespace"
:title="namespace"
- >{{truncatedNamespace}}</div>
+ >{{ truncatedNamespace }}</div>
</div>
</a>
</li>
diff --git a/app/assets/javascripts/projects_dropdown/components/search.vue b/app/assets/javascripts/projects_dropdown/components/search.vue
index 53bc76d0f2d..0c46ed184be 100644
--- a/app/assets/javascripts/projects_dropdown/components/search.vue
+++ b/app/assets/javascripts/projects_dropdown/components/search.vue
@@ -1,47 +1,47 @@
<script>
-import _ from 'underscore';
-import eventHub from '../event_hub';
+ import _ from 'underscore';
+ import eventHub from '../event_hub';
-export default {
- data() {
- return {
- searchQuery: '',
- };
- },
- watch: {
- searchQuery() {
- this.handleInput();
+ export default {
+ data() {
+ return {
+ searchQuery: '',
+ };
},
- },
- methods: {
- setFocus() {
- this.$refs.search.focus();
+ watch: {
+ searchQuery() {
+ this.handleInput();
+ },
},
- emitSearchEvents() {
- if (this.searchQuery) {
- eventHub.$emit('searchProjects', this.searchQuery);
- } else {
- eventHub.$emit('searchCleared');
- }
+ mounted() {
+ eventHub.$on('dropdownOpen', this.setFocus);
},
- /**
- * Callback function within _.debounce is intentionally
- * kept as ES5 `function() {}` instead of ES6 `() => {}`
- * as it otherwise messes up function context
- * and component reference is no longer accessible via `this`
- */
- // eslint-disable-next-line func-names
- handleInput: _.debounce(function () {
- this.emitSearchEvents();
- }, 500),
- },
- mounted() {
- eventHub.$on('dropdownOpen', this.setFocus);
- },
- beforeDestroy() {
- eventHub.$off('dropdownOpen', this.setFocus);
- },
-};
+ beforeDestroy() {
+ eventHub.$off('dropdownOpen', this.setFocus);
+ },
+ methods: {
+ setFocus() {
+ this.$refs.search.focus();
+ },
+ emitSearchEvents() {
+ if (this.searchQuery) {
+ eventHub.$emit('searchProjects', this.searchQuery);
+ } else {
+ eventHub.$emit('searchCleared');
+ }
+ },
+ /**
+ * Callback function within _.debounce is intentionally
+ * kept as ES5 `function() {}` instead of ES6 `() => {}`
+ * as it otherwise messes up function context
+ * and component reference is no longer accessible via `this`
+ */
+ // eslint-disable-next-line func-names
+ handleInput: _.debounce(function () {
+ this.emitSearchEvents();
+ }, 500),
+ },
+ };
</script>
<template>
@@ -59,6 +59,7 @@ export default {
v-if="!searchQuery"
class="search-icon fa fa-fw fa-search"
aria-hidden="true"
- />
+ >
+ </i>
</div>
</template>
diff --git a/app/assets/javascripts/registry/components/app.vue b/app/assets/javascripts/registry/components/app.vue
index 2d8ca443ea7..ea0f7199a70 100644
--- a/app/assets/javascripts/registry/components/app.vue
+++ b/app/assets/javascripts/registry/components/app.vue
@@ -1,14 +1,17 @@
<script>
- /* globals Flash */
import { mapGetters, mapActions } from 'vuex';
- import '../../flash';
+ import Flash from '../../flash';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import store from '../stores';
import collapsibleContainer from './collapsible_container.vue';
import { errorMessages, errorMessagesTypes } from '../constants';
export default {
- name: 'registryListApp',
+ name: 'RegistryListApp',
+ components: {
+ collapsibleContainer,
+ loadingIcon,
+ },
props: {
endpoint: {
type: String,
@@ -16,22 +19,12 @@
},
},
store,
- components: {
- collapsibleContainer,
- loadingIcon,
- },
computed: {
...mapGetters([
'isLoading',
'repos',
]),
},
- methods: {
- ...mapActions([
- 'setMainEndpoint',
- 'fetchRepos',
- ]),
- },
created() {
this.setMainEndpoint(this.endpoint);
},
@@ -39,6 +32,12 @@
this.fetchRepos()
.catch(() => Flash(errorMessages[errorMessagesTypes.FETCH_REPOS]));
},
+ methods: {
+ ...mapActions([
+ 'setMainEndpoint',
+ 'fetchRepos',
+ ]),
+ },
};
</script>
<template>
@@ -46,17 +45,18 @@
<loading-icon
v-if="isLoading"
size="3"
- />
+ />
<collapsible-container
v-else-if="!isLoading && repos.length"
v-for="(item, index) in repos"
:key="index"
:repo="item"
- />
+ />
<p v-else-if="!isLoading && !repos.length">
- {{__("No container images stored for this project. Add one by following the instructions above.")}}
+ {{ __(`No container images stored for this project.
+Add one by following the instructions above.`) }}
</p>
</div>
</template>
diff --git a/app/assets/javascripts/registry/components/collapsible_container.vue b/app/assets/javascripts/registry/components/collapsible_container.vue
index ac1c3ec253c..b4906ba4ee5 100644
--- a/app/assets/javascripts/registry/components/collapsible_container.vue
+++ b/app/assets/javascripts/registry/components/collapsible_container.vue
@@ -1,7 +1,6 @@
<script>
- /* globals Flash */
import { mapActions } from 'vuex';
- import '../../flash';
+ import Flash from '../../flash';
import clipboardButton from '../../vue_shared/components/clipboard_button.vue';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
@@ -9,13 +8,7 @@
import { errorMessages, errorMessagesTypes } from '../constants';
export default {
- name: 'collapsibeContainerRegisty',
- props: {
- repo: {
- type: Object,
- required: true,
- },
- },
+ name: 'CollapsibeContainerRegisty',
components: {
clipboardButton,
loadingIcon,
@@ -24,6 +17,12 @@
directives: {
tooltip,
},
+ props: {
+ repo: {
+ type: Object,
+ required: true,
+ },
+ },
data() {
return {
isOpen: false,
@@ -65,28 +64,29 @@
<template>
<div class="container-image">
- <div
- class="container-image-head">
+ <div class="container-image-head">
<button
type="button"
@click="toggleRepo"
- class="js-toggle-repo btn-link">
+ class="js-toggle-repo btn-link"
+ >
<i
class="fa"
:class="{
'fa-chevron-right': !isOpen,
'fa-chevron-up': isOpen,
}"
- aria-hidden="true">
+ aria-hidden="true"
+ >
</i>
- {{repo.name}}
+ {{ repo.name }}
</button>
<clipboard-button
v-if="repo.location"
:text="clipboardText"
:title="repo.location"
- />
+ />
<div class="controls hidden-xs pull-right">
<button
@@ -96,35 +96,38 @@
:title="s__('ContainerRegistry|Remove repository')"
:aria-label="s__('ContainerRegistry|Remove repository')"
v-tooltip
- @click="handleDeleteRepository">
+ @click="handleDeleteRepository"
+ >
<i
class="fa fa-trash"
- aria-hidden="true">
+ aria-hidden="true"
+ >
</i>
</button>
</div>
-
</div>
<loading-icon
v-if="repo.isLoading"
class="append-bottom-20"
size="2"
- />
+ />
<div
v-else-if="!repo.isLoading && isOpen"
- class="container-image-tags">
+ class="container-image-tags"
+ >
<table-registry
v-if="repo.list.length"
:repo="repo"
- />
+ />
<div
v-else
- class="nothing-here-block">
- {{s__("ContainerRegistry|No tags in Container Registry for this container image.")}}
+ class="nothing-here-block"
+ >
+ {{ s__("ContainerRegistry|No tags in Container Registry for this container image.") }}
</div>
</div>
</div>
diff --git a/app/assets/javascripts/registry/components/table_registry.vue b/app/assets/javascripts/registry/components/table_registry.vue
index 14d43e135fe..bef850eddc0 100644
--- a/app/assets/javascripts/registry/components/table_registry.vue
+++ b/app/assets/javascripts/registry/components/table_registry.vue
@@ -1,8 +1,7 @@
<script>
- /* globals Flash */
import { mapActions } from 'vuex';
import { n__ } from '../../locale';
- import '../../flash';
+ import Flash from '../../flash';
import clipboardButton from '../../vue_shared/components/clipboard_button.vue';
import tablePagination from '../../vue_shared/components/table_pagination.vue';
import tooltip from '../../vue_shared/directives/tooltip';
@@ -11,21 +10,21 @@
import { numberToHumanSize } from '../../lib/utils/number_utils';
export default {
- props: {
- repo: {
- type: Object,
- required: true,
- },
- },
components: {
clipboardButton,
tablePagination,
},
+ directives: {
+ tooltip,
+ },
mixins: [
timeagoMixin,
],
- directives: {
- tooltip,
+ props: {
+ repo: {
+ type: Object,
+ required: true,
+ },
},
computed: {
shouldRenderPagination() {
@@ -68,75 +67,78 @@
};
</script>
<template>
-<div>
- <table class="table tags">
- <thead>
- <tr>
- <th>{{s__('ContainerRegistry|Tag')}}</th>
- <th>{{s__('ContainerRegistry|Tag ID')}}</th>
- <th>{{s__("ContainerRegistry|Size")}}</th>
- <th>{{s__("ContainerRegistry|Created")}}</th>
- <th></th>
- </tr>
- </thead>
- <tbody>
- <tr
- v-for="(item, i) in repo.list"
- :key="i">
- <td>
+ <div>
+ <table class="table tags">
+ <thead>
+ <tr>
+ <th>{{ s__('ContainerRegistry|Tag') }}</th>
+ <th>{{ s__('ContainerRegistry|Tag ID') }}</th>
+ <th>{{ s__("ContainerRegistry|Size") }}</th>
+ <th>{{ s__("ContainerRegistry|Created") }}</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr
+ v-for="(item, i) in repo.list"
+ :key="i">
+ <td>
- {{item.tag}}
+ {{ item.tag }}
- <clipboard-button
- v-if="item.location"
- :title="item.location"
- :text="clipboardText(item.location)"
+ <clipboard-button
+ v-if="item.location"
+ :title="item.location"
+ :text="clipboardText(item.location)"
/>
- </td>
- <td>
- <span
- v-tooltip
- :title="item.revision"
- data-placement="bottom">
- {{item.shortRevision}}
+ </td>
+ <td>
+ <span
+ v-tooltip
+ :title="item.revision"
+ data-placement="bottom"
+ >
+ {{ item.shortRevision }}
</span>
- </td>
- <td>
- {{formatSize(item.size)}}
- <template v-if="item.size && item.layers">
- &middot;
- </template>
- {{layers(item)}}
- </td>
+ </td>
+ <td>
+ {{ formatSize(item.size) }}
+ <template v-if="item.size && item.layers">
+ &middot;
+ </template>
+ {{ layers(item) }}
+ </td>
- <td>
- {{timeFormated(item.createdAt)}}
- </td>
+ <td>
+ {{ timeFormated(item.createdAt) }}
+ </td>
- <td class="content">
- <button
- v-if="item.canDelete"
- type="button"
- class="js-delete-registry btn btn-danger hidden-xs pull-right"
- :title="s__('ContainerRegistry|Remove tag')"
- :aria-label="s__('ContainerRegistry|Remove tag')"
- data-container="body"
- v-tooltip
- @click="handleDeleteRegistry(item)">
- <i
- class="fa fa-trash"
- aria-hidden="true">
- </i>
- </button>
- </td>
- </tr>
- </tbody>
- </table>
+ <td class="content">
+ <button
+ v-if="item.canDelete"
+ type="button"
+ class="js-delete-registry btn btn-danger hidden-xs pull-right"
+ :title="s__('ContainerRegistry|Remove tag')"
+ :aria-label="s__('ContainerRegistry|Remove tag')"
+ data-container="body"
+ v-tooltip
+ @click="handleDeleteRegistry(item)"
+ >
+ <i
+ class="fa fa-trash"
+ aria-hidden="true"
+ >
+ </i>
+ </button>
+ </td>
+ </tr>
+ </tbody>
+ </table>
- <table-pagination
- v-if="shouldRenderPagination"
- :change="onPageChange"
- :page-info="repo.pagination"
+ <table-pagination
+ v-if="shouldRenderPagination"
+ :change="onPageChange"
+ :page-info="repo.pagination"
/>
-</div>
+ </div>
</template>
diff --git a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
index 6ee4d487c0b..839f9ec88b9 100644
--- a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
+++ b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
@@ -1,48 +1,51 @@
<script>
-import Flash from '../../../flash';
-import editForm from './edit_form.vue';
-import Icon from '../../../vue_shared/components/icon.vue';
+ import Flash from '../../../flash';
+ import editForm from './edit_form.vue';
+ import Icon from '../../../vue_shared/components/icon.vue';
-export default {
- components: {
- editForm,
- Icon,
- },
- props: {
- isConfidential: {
- required: true,
- type: Boolean,
+ export default {
+ components: {
+ editForm,
+ Icon,
},
- isEditable: {
- required: true,
- type: Boolean,
+ props: {
+ isConfidential: {
+ required: true,
+ type: Boolean,
+ },
+ isEditable: {
+ required: true,
+ type: Boolean,
+ },
+ service: {
+ required: true,
+ type: Object,
+ },
},
- service: {
- required: true,
- type: Object,
+ data() {
+ return {
+ edit: false,
+ };
},
- },
- data() {
- return {
- edit: false,
- };
- },
- computed: {
- confidentialityIcon() {
- return this.isConfidential ? 'eye-slash' : 'eye';
+ computed: {
+ confidentialityIcon() {
+ return this.isConfidential ? 'eye-slash' : 'eye';
+ },
},
- },
- methods: {
- toggleForm() {
- this.edit = !this.edit;
+ methods: {
+ toggleForm() {
+ this.edit = !this.edit;
+ },
+ updateConfidentialAttribute(confidential) {
+ this.service.update('issue', { confidential })
+ .then(() => location.reload())
+ .catch(() => {
+ Flash(`Something went wrong trying to
+ change the confidentiality of this issue`);
+ });
+ },
},
- updateConfidentialAttribute(confidential) {
- this.service.update('issue', { confidential })
- .then(() => location.reload())
- .catch(() => new Flash('Something went wrong trying to change the confidentiality of this issue'));
- },
- },
-};
+ };
</script>
<template>
@@ -51,8 +54,8 @@ export default {
<icon
:name="confidentialityIcon"
:size="16"
- aria-hidden="true">
- </icon>
+ aria-hidden="true"
+ />
</div>
<div class="title hide-collapsed">
Confidentiality
@@ -72,22 +75,26 @@ export default {
:is-confidential="isConfidential"
:update-confidential-attribute="updateConfidentialAttribute"
/>
- <div v-if="!isConfidential" class="no-value sidebar-item-value">
+ <div
+ v-if="!isConfidential"
+ class="no-value sidebar-item-value">
<icon
name="eye"
:size="16"
aria-hidden="true"
- class="sidebar-item-icon inline">
- </icon>
+ class="sidebar-item-icon inline"
+ />
Not confidential
</div>
- <div v-else class="value sidebar-item-value hide-collapsed">
+ <div
+ v-else
+ class="value sidebar-item-value hide-collapsed">
<icon
name="eye-slash"
:size="16"
aria-hidden="true"
- class="sidebar-item-icon inline is-active">
- </icon>
+ class="sidebar-item-icon inline is-active"
+ />
This issue is confidential
</div>
</div>
diff --git a/app/assets/javascripts/sidebar/components/confidential/edit_form.vue b/app/assets/javascripts/sidebar/components/confidential/edit_form.vue
index dd17b5abd46..6a81235a1a7 100644
--- a/app/assets/javascripts/sidebar/components/confidential/edit_form.vue
+++ b/app/assets/javascripts/sidebar/components/confidential/edit_form.vue
@@ -1,26 +1,25 @@
<script>
-import editFormButtons from './edit_form_buttons.vue';
+ import editFormButtons from './edit_form_buttons.vue';
-export default {
- props: {
- isConfidential: {
- required: true,
- type: Boolean,
+ export default {
+ components: {
+ editFormButtons,
},
- toggleForm: {
- required: true,
- type: Function,
+ props: {
+ isConfidential: {
+ required: true,
+ type: Boolean,
+ },
+ toggleForm: {
+ required: true,
+ type: Function,
+ },
+ updateConfidentialAttribute: {
+ required: true,
+ type: Function,
+ },
},
- updateConfidentialAttribute: {
- required: true,
- type: Function,
- },
- },
-
- components: {
- editFormButtons,
- },
-};
+ };
</script>
<template>
diff --git a/app/assets/javascripts/sidebar/components/lock/edit_form.vue b/app/assets/javascripts/sidebar/components/lock/edit_form.vue
index 242e826d471..e7a87636aa7 100644
--- a/app/assets/javascripts/sidebar/components/lock/edit_form.vue
+++ b/app/assets/javascripts/sidebar/components/lock/edit_form.vue
@@ -1,45 +1,47 @@
<script>
-import editFormButtons from './edit_form_buttons.vue';
-import issuableMixin from '../../../vue_shared/mixins/issuable';
+ import editFormButtons from './edit_form_buttons.vue';
+ import issuableMixin from '../../../vue_shared/mixins/issuable';
-export default {
- props: {
- isLocked: {
- required: true,
- type: Boolean,
+ export default {
+ components: {
+ editFormButtons,
},
-
- toggleForm: {
- required: true,
- type: Function,
- },
-
- updateLockedAttribute: {
- required: true,
- type: Function,
+ mixins: [
+ issuableMixin,
+ ],
+ props: {
+ isLocked: {
+ required: true,
+ type: Boolean,
+ },
+
+ toggleForm: {
+ required: true,
+ type: Function,
+ },
+
+ updateLockedAttribute: {
+ required: true,
+ type: Function,
+ },
},
- },
-
- mixins: [
- issuableMixin,
- ],
-
- components: {
- editFormButtons,
- },
-};
+ };
</script>
<template>
<div class="dropdown open">
<div class="dropdown-menu sidebar-item-warning-message">
- <p class="text" v-if="isLocked">
+ <p
+ class="text"
+ v-if="isLocked">
Unlock this {{ issuableDisplayName }}?
<strong>Everyone</strong>
will be able to comment.
</p>
- <p class="text" v-else>
+ <p
+ class="text"
+ v-else>
Lock this {{ issuableDisplayName }}?
Only
<strong>project members</strong>
diff --git a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
index 04c3a96bf74..02876a6c175 100644
--- a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
+++ b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
@@ -1,63 +1,63 @@
<script>
-/* global Flash */
-import editForm from './edit_form.vue';
-import issuableMixin from '../../../vue_shared/mixins/issuable';
-import Icon from '../../../vue_shared/components/icon.vue';
+ import Flash from '../../../flash';
+ import editForm from './edit_form.vue';
+ import issuableMixin from '../../../vue_shared/mixins/issuable';
+ import Icon from '../../../vue_shared/components/icon.vue';
-export default {
- props: {
- isLocked: {
- required: true,
- type: Boolean,
+ export default {
+ components: {
+ editForm,
+ Icon,
},
+ mixins: [
+ issuableMixin,
+ ],
- isEditable: {
- required: true,
- type: Boolean,
- },
-
- mediator: {
- required: true,
- type: Object,
- validator(mediatorObject) {
- return mediatorObject.service && mediatorObject.service.update && mediatorObject.store;
+ props: {
+ isLocked: {
+ required: true,
+ type: Boolean,
},
- },
- },
-
- mixins: [
- issuableMixin,
- ],
- components: {
- editForm,
- Icon,
- },
+ isEditable: {
+ required: true,
+ type: Boolean,
+ },
- computed: {
- lockIcon() {
- return this.isLocked ? 'lock' : 'lock-open';
+ mediator: {
+ required: true,
+ type: Object,
+ validator(mediatorObject) {
+ return mediatorObject.service && mediatorObject.service.update && mediatorObject.store;
+ },
+ },
},
- isLockDialogOpen() {
- return this.mediator.store.isLockDialogOpen;
- },
- },
+ computed: {
+ lockIcon() {
+ return this.isLocked ? 'lock' : 'lock-open';
+ },
- methods: {
- toggleForm() {
- this.mediator.store.isLockDialogOpen = !this.mediator.store.isLockDialogOpen;
+ isLockDialogOpen() {
+ return this.mediator.store.isLockDialogOpen;
+ },
},
- updateLockedAttribute(locked) {
- this.mediator.service.update(this.issuableType, {
- discussion_locked: locked,
- })
- .then(() => location.reload())
- .catch(() => Flash(this.__(`Something went wrong trying to change the locked state of this ${this.issuableDisplayName}`)));
+ methods: {
+ toggleForm() {
+ this.mediator.store.isLockDialogOpen = !this.mediator.store.isLockDialogOpen;
+ },
+
+ updateLockedAttribute(locked) {
+ this.mediator.service.update(this.issuableType, {
+ discussion_locked: locked,
+ })
+ .then(() => location.reload())
+ .catch(() => Flash(this.__(`Something went wrong trying to
+ change the locked state of this ${this.issuableDisplayName}`)));
+ },
},
- },
-};
+ };
</script>
<template>
@@ -67,8 +67,8 @@ export default {
:name="lockIcon"
:size="16"
aria-hidden="true"
- class="sidebar-item-icon is-active">
- </icon>
+ class="sidebar-item-icon is-active"
+ />
</div>
<div class="title hide-collapsed">
@@ -100,8 +100,8 @@ export default {
name="lock"
:size="16"
aria-hidden="true"
- class="sidebar-item-icon inline is-active">
- </icon>
+ class="sidebar-item-icon inline is-active"
+ />
{{ __('Locked') }}
</div>
@@ -113,8 +113,8 @@ export default {
name="lock-open"
:size="16"
aria-hidden="true"
- class="sidebar-item-icon inline">
- </icon>
+ class="sidebar-item-icon inline"
+ />
{{ __('Unlocked') }}
</div>
</div>
diff --git a/app/assets/javascripts/sidebar/components/participants/participants.vue b/app/assets/javascripts/sidebar/components/participants/participants.vue
index b8510a6ce3a..006a6d2905d 100644
--- a/app/assets/javascripts/sidebar/components/participants/participants.vue
+++ b/app/assets/javascripts/sidebar/components/participants/participants.vue
@@ -1,73 +1,73 @@
<script>
-import { __, n__, sprintf } from '../../../locale';
-import loadingIcon from '../../../vue_shared/components/loading_icon.vue';
-import userAvatarImage from '../../../vue_shared/components/user_avatar/user_avatar_image.vue';
+ import { __, n__, sprintf } from '../../../locale';
+ import loadingIcon from '../../../vue_shared/components/loading_icon.vue';
+ import userAvatarImage from '../../../vue_shared/components/user_avatar/user_avatar_image.vue';
-export default {
- props: {
- loading: {
- type: Boolean,
- required: false,
- default: false,
+ export default {
+ components: {
+ loadingIcon,
+ userAvatarImage,
},
- participants: {
- type: Array,
- required: false,
- default: () => [],
+ props: {
+ loading: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ participants: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ numberOfLessParticipants: {
+ type: Number,
+ required: false,
+ default: 7,
+ },
},
- numberOfLessParticipants: {
- type: Number,
- required: false,
- default: 7,
+ data() {
+ return {
+ isShowingMoreParticipants: false,
+ };
},
- },
- data() {
- return {
- isShowingMoreParticipants: false,
- };
- },
- components: {
- loadingIcon,
- userAvatarImage,
- },
- computed: {
- lessParticipants() {
- return this.participants.slice(0, this.numberOfLessParticipants);
- },
- visibleParticipants() {
- return this.isShowingMoreParticipants ? this.participants : this.lessParticipants;
- },
- hasMoreParticipants() {
- return this.participants.length > this.numberOfLessParticipants;
- },
- toggleLabel() {
- let label = '';
- if (this.isShowingMoreParticipants) {
- label = __('- show less');
- } else {
- label = sprintf(__('+ %{moreCount} more'), {
- moreCount: this.participants.length - this.numberOfLessParticipants,
- });
- }
+ computed: {
+ lessParticipants() {
+ return this.participants.slice(0, this.numberOfLessParticipants);
+ },
+ visibleParticipants() {
+ return this.isShowingMoreParticipants ? this.participants : this.lessParticipants;
+ },
+ hasMoreParticipants() {
+ return this.participants.length > this.numberOfLessParticipants;
+ },
+ toggleLabel() {
+ let label = '';
+ if (this.isShowingMoreParticipants) {
+ label = __('- show less');
+ } else {
+ label = sprintf(__('+ %{moreCount} more'), {
+ moreCount: this.participants.length - this.numberOfLessParticipants,
+ });
+ }
- return label;
- },
- participantLabel() {
- return sprintf(
- n__('%{count} participant', '%{count} participants', this.participants.length),
- { count: this.loading ? '' : this.participantCount },
- );
- },
- participantCount() {
- return this.participants.length;
+ return label;
+ },
+ participantLabel() {
+ return sprintf(
+ n__('%{count} participant', '%{count} participants', this.participants.length),
+ { count: this.loading ? '' : this.participantCount },
+ );
+ },
+ participantCount() {
+ return this.participants.length;
+ },
},
- },
- methods: {
- toggleMoreParticipants() {
- this.isShowingMoreParticipants = !this.isShowingMoreParticipants;
+ methods: {
+ toggleMoreParticipants() {
+ this.isShowingMoreParticipants = !this.isShowingMoreParticipants;
+ },
},
- },
-};
+ };
</script>
<template>
@@ -75,14 +75,17 @@ export default {
<div class="sidebar-collapsed-icon">
<i
class="fa fa-users"
- aria-hidden="true">
+ aria-hidden="true"
+ >
</i>
<loading-icon
v-if="loading"
- class="js-participants-collapsed-loading-icon" />
+ class="js-participants-collapsed-loading-icon"
+ />
<span
v-else
- class="js-participants-collapsed-count">
+ class="js-participants-collapsed-count"
+ >
{{ participantCount }}
</span>
</div>
@@ -90,34 +93,40 @@ export default {
<loading-icon
v-if="loading"
:inline="true"
- class="js-participants-expanded-loading-icon" />
+ class="js-participants-expanded-loading-icon"
+ />
{{ participantLabel }}
</div>
<div class="participants-list hide-collapsed">
<div
v-for="participant in visibleParticipants"
:key="participant.id"
- class="participants-author js-participants-author">
+ class="participants-author js-participants-author"
+ >
<a
class="author_link"
- :href="participant.web_url">
+ :href="participant.web_url"
+ >
<user-avatar-image
:lazy="true"
:img-src="participant.avatar_url"
css-classes="avatar-inline"
:size="24"
:tooltip-text="participant.name"
- tooltip-placement="bottom" />
+ tooltip-placement="bottom"
+ />
</a>
</div>
</div>
<div
v-if="hasMoreParticipants"
- class="participants-more hide-collapsed">
+ class="participants-more hide-collapsed"
+ >
<button
type="button"
class="btn-transparent btn-blank js-toggle-participants-button"
- @click="toggleMoreParticipants">
+ @click="toggleMoreParticipants"
+ >
{{ toggleLabel }}
</button>
</div>
diff --git a/app/assets/javascripts/sidebar/components/participants/sidebar_participants.vue b/app/assets/javascripts/sidebar/components/participants/sidebar_participants.vue
index 6fcd2f95309..5c1ead1a8ac 100644
--- a/app/assets/javascripts/sidebar/components/participants/sidebar_participants.vue
+++ b/app/assets/javascripts/sidebar/components/participants/sidebar_participants.vue
@@ -1,23 +1,23 @@
<script>
-import Store from '../../stores/sidebar_store';
-import participants from './participants.vue';
+ import Store from '../../stores/sidebar_store';
+ import participants from './participants.vue';
-export default {
- data() {
- return {
- store: new Store(),
- };
- },
- props: {
- mediator: {
- type: Object,
- required: true,
+ export default {
+ components: {
+ participants,
},
- },
- components: {
- participants,
- },
-};
+ props: {
+ mediator: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ store: new Store(),
+ };
+ },
+ };
</script>
<template>
diff --git a/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions.vue b/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions.vue
index f4bae1d3dd5..3e8cc7a6630 100644
--- a/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions.vue
+++ b/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions.vue
@@ -6,10 +6,8 @@ import { __ } from '../../../locale';
import subscriptions from './subscriptions.vue';
export default {
- data() {
- return {
- store: new Store(),
- };
+ components: {
+ subscriptions,
},
props: {
mediator: {
@@ -17,10 +15,17 @@ export default {
required: true,
},
},
- components: {
- subscriptions,
+ data() {
+ return {
+ store: new Store(),
+ };
+ },
+ created() {
+ eventHub.$on('toggleSubscription', this.onToggleSubscription);
+ },
+ beforeDestroy() {
+ eventHub.$off('toggleSubscription', this.onToggleSubscription);
},
-
methods: {
onToggleSubscription() {
this.mediator.toggleSubscription()
@@ -29,14 +34,6 @@ export default {
});
},
},
-
- created() {
- eventHub.$on('toggleSubscription', this.onToggleSubscription);
- },
-
- beforeDestroy() {
- eventHub.$off('toggleSubscription', this.onToggleSubscription);
- },
};
</script>
@@ -44,6 +41,7 @@ export default {
<div class="block subscriptions">
<subscriptions
:loading="store.isFetching.subscriptions"
- :subscribed="store.subscribed" />
+ :subscribed="store.subscribed"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue b/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue
index 940e1764f3d..7226076a8fc 100644
--- a/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue
+++ b/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue
@@ -1,45 +1,46 @@
<script>
-import { __ } from '../../../locale';
-import eventHub from '../../event_hub';
-import loadingButton from '../../../vue_shared/components/loading_button.vue';
+ /* eslint-disable vue/require-default-prop */
+ import { __ } from '../../../locale';
+ import eventHub from '../../event_hub';
+ import loadingButton from '../../../vue_shared/components/loading_button.vue';
-export default {
- props: {
- loading: {
- type: Boolean,
- required: false,
- default: false,
+ export default {
+ components: {
+ loadingButton,
},
- subscribed: {
- type: Boolean,
- required: false,
+ props: {
+ loading: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ subscribed: {
+ type: Boolean,
+ required: false,
+ },
+ id: {
+ type: Number,
+ required: false,
+ },
},
- id: {
- type: Number,
- required: false,
- },
- },
- components: {
- loadingButton,
- },
- computed: {
- buttonLabel() {
- let label;
- if (this.subscribed === false) {
- label = __('Subscribe');
- } else if (this.subscribed === true) {
- label = __('Unsubscribe');
- }
+ computed: {
+ buttonLabel() {
+ let label;
+ if (this.subscribed === false) {
+ label = __('Subscribe');
+ } else if (this.subscribed === true) {
+ label = __('Unsubscribe');
+ }
- return label;
+ return label;
+ },
},
- },
- methods: {
- toggleSubscription() {
- eventHub.$emit('toggleSubscription', this.id);
+ methods: {
+ toggleSubscription() {
+ eventHub.$emit('toggleSubscription', this.id);
+ },
},
- },
-};
+ };
</script>
<template>
@@ -47,7 +48,8 @@ export default {
<div class="sidebar-collapsed-icon">
<i
class="fa fa-rss"
- aria-hidden="true">
+ aria-hidden="true"
+ >
</i>
</div>
<span class="issuable-header-text hide-collapsed pull-left">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
index dbc65462377..109a302a172 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
@@ -1,10 +1,16 @@
<script>
+ /* eslint-disable vue/require-default-prop */
import pipelineStage from '../../pipelines/components/stage.vue';
import ciIcon from '../../vue_shared/components/ci_icon.vue';
import icon from '../../vue_shared/components/icon.vue';
export default {
name: 'MRWidgetPipeline',
+ components: {
+ pipelineStage,
+ ciIcon,
+ icon,
+ },
props: {
pipeline: {
type: Object,
@@ -21,11 +27,6 @@
required: false,
},
},
- components: {
- pipelineStage,
- ciIcon,
- icon,
- },
computed: {
hasPipeline() {
return this.pipeline && Object.keys(this.pipeline).length > 0;
@@ -62,7 +63,8 @@
<template v-else-if="hasPipeline">
<a
class="append-right-10"
- :href="this.status.details_path">
+ :href="status.details_path"
+ >
<ci-icon :status="status" />
</a>
@@ -70,33 +72,37 @@
Pipeline
<a
:href="pipeline.path"
- class="pipeline-id">
- #{{pipeline.id}}
+ class="pipeline-id"
+ >
+ #{{ pipeline.id }}
</a>
- {{pipeline.details.status.label}} for
+ {{ pipeline.details.status.label }} for
<a
:href="pipeline.commit.commit_path"
- class="commit-sha js-commit-link">
- {{pipeline.commit.short_id}}</a>.
+ class="commit-sha js-commit-link"
+ >
+ {{ pipeline.commit.short_id }}</a>.
<span class="mr-widget-pipeline-graph">
- <span class="stage-cell">
+ <span
+ class="stage-cell"
+ v-if="hasStages"
+ >
<div
- v-if="hasStages"
v-for="(stage, i) in pipeline.details.stages"
:key="i"
- class="stage-container dropdown js-mini-pipeline-graph">
+ class="stage-container dropdown js-mini-pipeline-graph"
+ >
<pipeline-stage :stage="stage" />
</div>
</span>
</span>
<template v-if="pipeline.coverage">
- Coverage {{pipeline.coverage}}%
+ Coverage {{ pipeline.coverage }}%
</template>
-
</div>
</template>
</div>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
index 09276ba2769..52dd0245ff0 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
@@ -7,6 +7,10 @@
export default {
name: 'MRWidgetRebase',
+ components: {
+ statusIcon,
+ loadingIcon,
+ },
props: {
mr: {
type: Object,
@@ -17,10 +21,6 @@
required: true,
},
},
- components: {
- statusIcon,
- loadingIcon,
- },
data() {
return {
isMakingRequest: false,
@@ -88,7 +88,7 @@
<status-icon
:status="status"
:show-disabled-button="showDisabledButton"
- />
+ />
<div class="rebase-state-find-class-convention media media-body space-children">
<template v-if="mr.rebaseInProgress || isMakingRequest">
@@ -100,23 +100,27 @@
<span class="bold">
Fast-forward merge is not possible.
Rebase the source branch onto
- <span class="label-branch">{{mr.targetBranch}}</span>
+ <span class="label-branch">{{ mr.targetBranch }}</span>
to allow this merge request to be merged.
</span>
</template>
<template v-if="!mr.rebaseInProgress && mr.canPushToSourceBranch && !isMakingRequest">
- <div class="accept-merge-holder clearfix js-toggle-container accept-action media space-children">
+ <div
+ class="accept-merge-holder clearfix
+js-toggle-container accept-action media space-children">
<button
type="button"
class="btn btn-sm btn-reopen btn-success"
:disabled="isMakingRequest"
- @click="rebase">
+ @click="rebase"
+ >
<loading-icon v-if="isMakingRequest" />
Rebase
</button>
<span
v-if="!rebasingError"
- class="bold">
+ class="bold"
+ >
Fast-forward merge is not possible.
Rebase the source branch onto the target branch or merge target
branch into source branch to allow this merge request to be merged.
@@ -124,7 +128,7 @@
<span
v-else
class="bold danger">
- {{rebasingError}}
+ {{ rebasingError }}
</span>
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/ci_badge_link.vue b/app/assets/javascripts/vue_shared/components/ci_badge_link.vue
index fc795936abf..5324d5dc797 100644
--- a/app/assets/javascripts/vue_shared/components/ci_badge_link.vue
+++ b/app/assets/javascripts/vue_shared/components/ci_badge_link.vue
@@ -23,6 +23,12 @@
*/
export default {
+ components: {
+ ciIcon,
+ },
+ directives: {
+ tooltip,
+ },
props: {
status: {
type: Object,
@@ -34,12 +40,6 @@
default: true,
},
},
- components: {
- ciIcon,
- },
- directives: {
- tooltip,
- },
computed: {
cssClass() {
const className = this.status.group;
@@ -53,11 +53,12 @@
:href="status.details_path"
:class="cssClass"
v-tooltip
- :title="!showText ? status.text : ''">
+ :title="!showText ? status.text : ''"
+ >
<ci-icon :status="status" />
<template v-if="showText">
- {{status.text}}
+ {{ status.text }}
</template>
</a>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/ci_icon.vue b/app/assets/javascripts/vue_shared/components/ci_icon.vue
index 2a018f38366..8fea746f4de 100644
--- a/app/assets/javascripts/vue_shared/components/ci_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/ci_icon.vue
@@ -23,6 +23,9 @@
* - Jobs show view sidebar
*/
export default {
+ components: {
+ icon,
+ },
props: {
status: {
type: Object,
@@ -30,10 +33,6 @@
},
},
- components: {
- icon,
- },
-
computed: {
cssClass() {
const status = this.status.group;
@@ -43,9 +42,7 @@
};
</script>
<template>
- <span
- :class="cssClass">
- <icon
- :name="status.icon"/>
+ <span :class="cssClass">
+ <icon :name="status.icon" />
</span>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/clipboard_button.vue b/app/assets/javascripts/vue_shared/components/clipboard_button.vue
index 3a7143c450e..31d9b9d9c48 100644
--- a/app/assets/javascripts/vue_shared/components/clipboard_button.vue
+++ b/app/assets/javascripts/vue_shared/components/clipboard_button.vue
@@ -1,10 +1,14 @@
<script>
+ import tooltip from '../directives/tooltip';
/**
* Falls back to the code used in `copy_to_clipboard.js`
*/
export default {
- name: 'clipboardButton',
+ name: 'ClipboardButton',
+ directives: {
+ tooltip,
+ },
props: {
text: {
type: String,
@@ -14,6 +18,16 @@
type: String,
required: true,
},
+ tooltipPlacement: {
+ type: String,
+ required: false,
+ default: 'top',
+ },
+ tooltipContainer: {
+ type: [String, Boolean],
+ required: false,
+ default: false,
+ },
},
};
</script>
@@ -22,11 +36,16 @@
<button
type="button"
class="btn btn-transparent btn-clipboard"
- :data-title="title"
- :data-clipboard-text="text">
- <i
- aria-hidden="true"
- class="fa fa-clipboard">
- </i>
+ :title="title"
+ :data-clipboard-text="text"
+ v-tooltip
+ :data-container="tooltipContainer"
+ :data-placement="tooltipPlacement"
+ >
+ <i
+ aria-hidden="true"
+ class="fa fa-clipboard"
+ >
+ </i>
</button>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/commit.vue b/app/assets/javascripts/vue_shared/components/commit.vue
index 59ca9a0a6d4..6d1fe7ee8ca 100644
--- a/app/assets/javascripts/vue_shared/components/commit.vue
+++ b/app/assets/javascripts/vue_shared/components/commit.vue
@@ -2,9 +2,16 @@
import commitIconSvg from 'icons/_icon_commit.svg';
import userAvatarLink from './user_avatar/user_avatar_link.vue';
import tooltip from '../directives/tooltip';
- import Icon from '../../vue_shared/components/icon.vue';
+ import icon from '../../vue_shared/components/icon.vue';
export default {
+ directives: {
+ tooltip,
+ },
+ components: {
+ userAvatarLink,
+ icon,
+ },
props: {
/**
* Indicates the existance of a tag.
@@ -103,13 +110,6 @@
this.author.username ? `${this.author.username}'s avatar` : null;
},
},
- directives: {
- tooltip,
- },
- components: {
- userAvatarLink,
- Icon,
- },
created() {
this.commitIconSvg = commitIconSvg;
},
@@ -118,17 +118,17 @@
<template>
<div class="branch-commit">
<template v-if="hasCommitRef && showBranch">
- <div
- class="icon-container hidden-xs">
+ <div class="icon-container hidden-xs">
<i
v-if="tag"
class="fa fa-tag"
- aria-hidden="true">
+ aria-hidden="true"
+ >
</i>
<icon
v-if="!tag"
- name="fork">
- </icon>
+ name="fork"
+ />
</div>
<a
@@ -136,25 +136,29 @@
:href="commitRef.ref_url"
v-tooltip
data-container="body"
- :title="commitRef.name">
- {{commitRef.name}}
+ :title="commitRef.name"
+ >
+ {{ commitRef.name }}
</a>
</template>
<div
v-html="commitIconSvg"
- class="commit-icon js-commit-icon">
+ class="commit-icon js-commit-icon"
+ >
</div>
<a
class="commit-sha"
- :href="commitUrl">
- {{shortSha}}
+ :href="commitUrl"
+ >
+ {{ shortSha }}
</a>
<div class="commit-title flex-truncate-parent">
<span
v-if="title"
- class="flex-truncate-child">
+ class="flex-truncate-child"
+ >
<user-avatar-link
v-if="hasAuthor"
class="avatar-image-container"
@@ -165,8 +169,9 @@
/>
<a
class="commit-row-message"
- :href="commitUrl">
- {{title}}
+ :href="commitUrl"
+ >
+ {{ title }}
</a>
</span>
<span v-else>
diff --git a/app/assets/javascripts/vue_shared/components/expand_button.vue b/app/assets/javascripts/vue_shared/components/expand_button.vue
index 05e48ed297f..3595a9389e9 100644
--- a/app/assets/javascripts/vue_shared/components/expand_button.vue
+++ b/app/assets/javascripts/vue_shared/components/expand_button.vue
@@ -11,7 +11,7 @@
* </expand-button>
*/
export default {
- name: 'expandButton',
+ name: 'ExpandButton',
data() {
return {
isCollapsed: true,
diff --git a/app/assets/javascripts/vue_shared/components/file_icon.vue b/app/assets/javascripts/vue_shared/components/file_icon.vue
index 65c64967fdc..c9d7c0f4999 100644
--- a/app/assets/javascripts/vue_shared/components/file_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/file_icon.vue
@@ -16,6 +16,10 @@
*/
export default {
+ components: {
+ loadingIcon,
+ icon,
+ },
props: {
fileName: {
type: String,
@@ -52,10 +56,6 @@
default: '',
},
},
- components: {
- loadingIcon,
- icon,
- },
computed: {
spriteHref() {
const iconName = getIconForFile(this.fileName) || 'file';
@@ -75,9 +75,9 @@
<span>
<svg
:class="[iconSizeClass, cssClasses]"
- v-if="!loading && !folder">
- <use
- v-bind="{'xlink:href':spriteHref}"/>
+ v-if="!loading && !folder"
+ >
+ <use v-bind="{ 'xlink:href':spriteHref }" />
</svg>
<icon
v-if="!loading && folder"
diff --git a/app/assets/javascripts/vue_shared/components/header_ci_component.vue b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
index 2209bc0f9cf..1f72dea1b33 100644
--- a/app/assets/javascripts/vue_shared/components/header_ci_component.vue
+++ b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
@@ -1,80 +1,78 @@
<script>
-import ciIconBadge from './ci_badge_link.vue';
-import loadingIcon from './loading_icon.vue';
-import timeagoTooltip from './time_ago_tooltip.vue';
-import tooltip from '../directives/tooltip';
-import userAvatarImage from './user_avatar/user_avatar_image.vue';
-
-/**
- * Renders header component for job and pipeline page based on UI mockups
- *
- * Used in:
- * - job show page
- * - pipeline show page
- */
-export default {
- props: {
- status: {
- type: Object,
- required: true,
- },
- itemName: {
- type: String,
- required: true,
- },
- itemId: {
- type: Number,
- required: true,
- },
- time: {
- type: String,
- required: true,
- },
- user: {
- type: Object,
- required: false,
- default: () => ({}),
+ import ciIconBadge from './ci_badge_link.vue';
+ import loadingIcon from './loading_icon.vue';
+ import timeagoTooltip from './time_ago_tooltip.vue';
+ import tooltip from '../directives/tooltip';
+ import userAvatarImage from './user_avatar/user_avatar_image.vue';
+
+ /**
+ * Renders header component for job and pipeline page based on UI mockups
+ *
+ * Used in:
+ * - job show page
+ * - pipeline show page
+ */
+ export default {
+ components: {
+ ciIconBadge,
+ loadingIcon,
+ timeagoTooltip,
+ userAvatarImage,
},
- actions: {
- type: Array,
- required: false,
- default: () => [],
+ directives: {
+ tooltip,
},
- hasSidebarButton: {
- type: Boolean,
- required: false,
- default: false,
+ props: {
+ status: {
+ type: Object,
+ required: true,
+ },
+ itemName: {
+ type: String,
+ required: true,
+ },
+ itemId: {
+ type: Number,
+ required: true,
+ },
+ time: {
+ type: String,
+ required: true,
+ },
+ user: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ actions: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ hasSidebarButton: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ shouldRenderTriggeredLabel: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
},
- shouldRenderTriggeredLabel: {
- type: Boolean,
- required: false,
- default: true,
- },
- },
-
- directives: {
- tooltip,
- },
-
- components: {
- ciIconBadge,
- loadingIcon,
- timeagoTooltip,
- userAvatarImage,
- },
- computed: {
- userAvatarAltText() {
- return `${this.user.name}'s avatar`;
+ computed: {
+ userAvatarAltText() {
+ return `${this.user.name}'s avatar`;
+ },
},
- },
- methods: {
- onClickAction(action) {
- this.$emit('actionClicked', action);
+ methods: {
+ onClickAction(action) {
+ this.$emit('actionClicked', action);
+ },
},
- },
-};
+ };
</script>
<template>
@@ -84,7 +82,7 @@ export default {
<ci-icon-badge :status="status" />
<strong>
- {{itemName}} #{{itemId}}
+ {{ itemName }} #{{ itemId }}
</strong>
<template v-if="shouldRenderTriggeredLabel">
@@ -103,16 +101,17 @@ export default {
v-tooltip
:href="user.path"
:title="user.email"
- class="js-user-link commit-committer-link">
+ class="js-user-link commit-committer-link"
+ >
<user-avatar-image
:img-src="user.avatar_url"
:img-alt="userAvatarAltText"
:tooltip-text="user.name"
:img-size="24"
- />
+ />
- {{user.name}}
+ {{ user.name }}
</a>
</template>
</section>
@@ -121,12 +120,15 @@ export default {
class="header-action-buttons"
v-if="actions.length">
<template
- v-for="action in actions">
+ v-for="(action, i) in actions"
+ >
<a
v-if="action.type === 'link'"
:href="action.path"
- :class="action.cssClass">
- {{action.label}}
+ :class="action.cssClass"
+ :key="i"
+ >
+ {{ action.label }}
</a>
<a
@@ -134,8 +136,10 @@ export default {
:href="action.path"
data-method="post"
rel="nofollow"
- :class="action.cssClass">
- {{action.label}}
+ :class="action.cssClass"
+ :key="i"
+ >
+ {{ action.label }}
</a>
<button
@@ -143,25 +147,31 @@ export default {
@click="onClickAction(action)"
:disabled="action.isLoading"
:class="action.cssClass"
- type="button">
- {{action.label}}
+ type="button"
+ :key="i"
+ >
+ {{ action.label }}
<i
v-show="action.isLoading"
class="fa fa-spin fa-spinner"
- aria-hidden="true">
+ aria-hidden="true"
+ >
</i>
</button>
</template>
<button
v-if="hasSidebarButton"
type="button"
- class="btn btn-default visible-xs-block visible-sm-block sidebar-toggle-btn js-sidebar-build-toggle js-sidebar-build-toggle-header"
+ class="btn btn-default visible-xs-block
+visible-sm-block sidebar-toggle-btn js-sidebar-build-toggle js-sidebar-build-toggle-header"
aria-label="Toggle Sidebar"
- id="toggleSidebar">
+ id="toggleSidebar"
+ >
<i
class="fa fa-angle-double-left"
aria-hidden="true"
- aria-labelledby="toggleSidebar">
+ aria-labelledby="toggleSidebar"
+ >
</i>
</button>
</section>
diff --git a/app/assets/javascripts/vue_shared/components/icon.vue b/app/assets/javascripts/vue_shared/components/icon.vue
index 365229ea274..6a2e05000e1 100644
--- a/app/assets/javascripts/vue_shared/components/icon.vue
+++ b/app/assets/javascripts/vue_shared/components/icon.vue
@@ -1,17 +1,17 @@
<script>
-/* This is a re-usable vue component for rendering a svg sprite
- icon
+ /* This is a re-usable vue component for rendering a svg sprite
+ icon
- Sample configuration:
+ Sample configuration:
- <icon
- name="retry"
- :size="32"
- css-classes="top"
- />
+ <icon
+ name="retry"
+ :size="32"
+ css-classes="top"
+ />
-*/
+ */
// only allow classes in images.scss e.g. s12
const validSizes = [8, 12, 16, 18, 24, 32, 48, 72];
@@ -80,7 +80,6 @@
:height="height"
:x="x"
:y="y">
- <use
- v-bind="{'xlink:href':spriteHref}"/>
+ <use v-bind="{ 'xlink:href':spriteHref }" />
</svg>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/identicon.vue b/app/assets/javascripts/vue_shared/components/identicon.vue
index 7cf2e029cf6..0a30f467b08 100644
--- a/app/assets/javascripts/vue_shared/components/identicon.vue
+++ b/app/assets/javascripts/vue_shared/components/identicon.vue
@@ -46,6 +46,6 @@ export default {
class="avatar identicon"
:class="sizeClass"
:style="identiconStyles">
- {{identiconTitle}}
+ {{ identiconTitle }}
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue b/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue
index 564fc5029af..b48828ae81f 100644
--- a/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue
+++ b/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue
@@ -1,7 +1,10 @@
<script>
- import Icon from '../../../vue_shared/components/icon.vue';
+ import icon from '../../../vue_shared/components/icon.vue';
export default {
+ components: {
+ icon,
+ },
props: {
isLocked: {
type: Boolean,
@@ -16,10 +19,6 @@
},
},
- components: {
- Icon,
- },
-
computed: {
warningIcon() {
if (this.isConfidential) return 'eye-slash';
@@ -37,16 +36,17 @@
<template>
<div class="issuable-note-warning">
<icon
- :name="warningIcon"
- :size="16"
- class="icon inline"
- aria-hidden="true"
- v-if="!isLockedAndConfidential">
- </icon>
+ :name="warningIcon"
+ :size="16"
+ class="icon inline"
+ aria-hidden="true"
+ v-if="!isLockedAndConfidential"
+ />
<span v-if="isLockedAndConfidential">
{{ __('This issue is confidential and locked.') }}
- {{ __('People without permission will never get a notification and won\'t be able to comment.') }}
+ {{ __(`People without permission will never
+get a notification and won't be able to comment.`) }}
</span>
<span v-else-if="isConfidential">
diff --git a/app/assets/javascripts/vue_shared/components/loading_button.vue b/app/assets/javascripts/vue_shared/components/loading_button.vue
index 247943f83e6..ff8c0f7c1d2 100644
--- a/app/assets/javascripts/vue_shared/components/loading_button.vue
+++ b/app/assets/javascripts/vue_shared/components/loading_button.vue
@@ -1,55 +1,56 @@
<script>
+ /* eslint-disable vue/require-default-prop */
-/* This is a re-usable vue component for rendering a button
- that will probably be sending off ajax requests and need
- to show the loading status by setting the `loading` option.
- This can also be used for initial page load when you don't
- know the action of the button yet by setting
- `loading: true, label: undefined`.
+ /* This is a re-usable vue component for rendering a button
+ that will probably be sending off ajax requests and need
+ to show the loading status by setting the `loading` option.
+ This can also be used for initial page load when you don't
+ know the action of the button yet by setting
+ `loading: true, label: undefined`.
- Sample configuration:
+ Sample configuration:
- <loading-button
- :loading="true"
- :label="Hello"
- @click="..."
- />
+ <loading-button
+ :loading="true"
+ :label="Hello"
+ @click="..."
+ />
-*/
+ */
-import loadingIcon from './loading_icon.vue';
+ import loadingIcon from './loading_icon.vue';
-export default {
- props: {
- loading: {
- type: Boolean,
- required: false,
- default: false,
+ export default {
+ components: {
+ loadingIcon,
},
- disabled: {
- type: Boolean,
- required: false,
- default: false,
+ props: {
+ loading: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ disabled: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ label: {
+ type: String,
+ required: false,
+ },
+ containerClass: {
+ type: String,
+ required: false,
+ default: 'btn btn-align-content',
+ },
},
- label: {
- type: String,
- required: false,
+ methods: {
+ onClick(e) {
+ this.$emit('click', e);
+ },
},
- containerClass: {
- type: String,
- required: false,
- default: 'btn btn-align-content',
- },
- },
- components: {
- loadingIcon,
- },
- methods: {
- onClick(e) {
- this.$emit('click', e);
- },
- },
-};
+ };
</script>
<template>
@@ -59,23 +60,23 @@ export default {
:class="containerClass"
:disabled="loading || disabled"
>
- <transition name="fade">
- <loading-icon
- v-if="loading"
- :inline="true"
- class="js-loading-button-icon"
- :class="{
- 'append-right-5': label
- }"
- />
- </transition>
- <transition name="fade">
- <span
- v-if="label"
- class="js-loading-button-label"
- >
- {{ label }}
- </span>
- </transition>
+ <transition name="fade">
+ <loading-icon
+ v-if="loading"
+ :inline="true"
+ class="js-loading-button-icon"
+ :class="{
+ 'append-right-5': label
+ }"
+ />
+ </transition>
+ <transition name="fade">
+ <span
+ v-if="label"
+ class="js-loading-button-label"
+ >
+ {{ label }}
+ </span>
+ </transition>
</button>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/loading_icon.vue b/app/assets/javascripts/vue_shared/components/loading_icon.vue
index 15581d5c2a0..1eba117b18f 100644
--- a/app/assets/javascripts/vue_shared/components/loading_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/loading_icon.vue
@@ -32,13 +32,14 @@
</script>
<template>
<component
- :is="this.rootElementType"
+ :is="rootElementType"
class="text-center">
<i
class="fa fa-spin fa-spinner"
:class="cssClass"
aria-hidden="true"
- :aria-label="label">
+ :aria-label="label"
+ >
</i>
</component>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
index 15e3d713448..1371dca0c35 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/field.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -6,6 +6,11 @@
import icon from '../icon.vue';
export default {
+ components: {
+ markdownHeader,
+ markdownToolbar,
+ icon,
+ },
props: {
markdownPreviewPath: {
type: String,
@@ -24,6 +29,7 @@
quickActionsDocsPath: {
type: String,
required: false,
+ default: '',
},
canAttachFile: {
type: Boolean,
@@ -45,17 +51,24 @@
previewMarkdown: false,
};
},
- components: {
- markdownHeader,
- markdownToolbar,
- icon,
- },
computed: {
shouldShowReferencedUsers() {
const referencedUsersThreshold = 10;
return this.referencedUsers.length >= referencedUsersThreshold;
},
},
+ mounted() {
+ /*
+ GLForm class handles all the toolbar buttons
+ */
+ return new GLForm($(this.$refs['gl-form']), this.enableAutocomplete);
+ },
+ beforeDestroy() {
+ const glForm = $(this.$refs['gl-form']).data('gl-form');
+ if (glForm) {
+ glForm.destroy();
+ }
+ },
methods: {
showPreviewTab() {
if (this.previewMarkdown) return;
@@ -98,18 +111,6 @@
});
},
},
- mounted() {
- /*
- GLForm class handles all the toolbar buttons
- */
- return new GLForm($(this.$refs['gl-form']), this.enableAutocomplete);
- },
- beforeDestroy() {
- const glForm = $(this.$refs['gl-form']).data('gl-form');
- if (glForm) {
- glForm.destroy();
- }
- },
};
</script>
@@ -121,34 +122,39 @@
<markdown-header
:preview-markdown="previewMarkdown"
@preview-markdown="showPreviewTab"
- @write-markdown="showWriteTab" />
+ @write-markdown="showWriteTab"
+ />
<div
class="md-write-holder"
- v-show="!previewMarkdown">
+ v-show="!previewMarkdown"
+ >
<div class="zen-backdrop">
<slot name="textarea"></slot>
<a
class="zen-control zen-control-leave js-zen-leave"
href="#"
- aria-label="Enter zen mode">
+ aria-label="Enter zen mode"
+ >
<icon
name="screen-normal"
- :size="32">
- </icon>
+ :size="32"
+ />
</a>
<markdown-toolbar
:markdown-docs-path="markdownDocsPath"
:quick-actions-docs-path="quickActionsDocsPath"
:can-attach-file="canAttachFile"
- />
+ />
</div>
</div>
<div
class="md md-preview-holder md-preview"
- v-show="previewMarkdown">
+ v-show="previewMarkdown"
+ >
<div
ref="markdown-preview"
- v-html="markdownPreview">
+ v-html="markdownPreview"
+ >
</div>
<span v-if="markdownPreviewLoading">
Loading...
@@ -158,23 +164,27 @@
<div
v-if="referencedCommands"
v-html="referencedCommands"
- class="referenced-commands"></div>
+ class="referenced-commands"
+ >
+ </div>
<div
v-if="shouldShowReferencedUsers"
- class="referenced-users">
- <span>
- <i
- class="fa fa-exclamation-triangle"
- aria-hidden="true">
- </i>
- You are about to add
- <strong>
- <span class="js-referenced-users-count">
- {{referencedUsers.length}}
- </span>
- </strong> people to the discussion. Proceed with caution.
- </span>
- </div>
+ class="referenced-users"
+ >
+ <span>
+ <i
+ class="fa fa-exclamation-triangle"
+ aria-hidden="true"
+ >
+ </i>
+ You are about to add
+ <strong>
+ <span class="js-referenced-users-count">
+ {{ referencedUsers.length }}
+ </span>
+ </strong> people to the discussion. Proceed with caution.
+ </span>
+ </div>
</template>
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue
index 36d2d1dc164..f65eab11a27 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue
@@ -4,18 +4,26 @@
import icon from '../icon.vue';
export default {
+ directives: {
+ tooltip,
+ },
+ components: {
+ toolbarButton,
+ icon,
+ },
props: {
previewMarkdown: {
type: Boolean,
required: true,
},
},
- directives: {
- tooltip,
+ mounted() {
+ $(document).on('markdown-preview:show.vue', this.previewMarkdownTab);
+ $(document).on('markdown-preview:hide.vue', this.writeMarkdownTab);
},
- components: {
- toolbarButton,
- icon,
+ beforeDestroy() {
+ $(document).off('markdown-preview:show.vue', this.previewMarkdownTab);
+ $(document).off('markdown-preview:hide.vue', this.writeMarkdownTab);
},
methods: {
isMarkdownForm(form) {
@@ -36,14 +44,6 @@
this.$emit('write-markdown');
},
},
- mounted() {
- $(document).on('markdown-preview:show.vue', this.previewMarkdownTab);
- $(document).on('markdown-preview:hide.vue', this.writeMarkdownTab);
- },
- beforeDestroy() {
- $(document).off('markdown-preview:show.vue', this.previewMarkdownTab);
- $(document).off('markdown-preview:hide.vue', this.writeMarkdownTab);
- },
};
</script>
@@ -52,12 +52,14 @@
<ul class="nav-links clearfix">
<li
class="md-header-tab"
- :class="{ active: !previewMarkdown }">
+ :class="{ active: !previewMarkdown }"
+ >
<a
class="js-write-link"
href="#md-write-holder"
tabindex="-1"
- @click.prevent="writeMarkdownTab($event)">
+ @click.prevent="writeMarkdownTab($event)"
+ >
Write
</a>
</li>
@@ -68,46 +70,55 @@
class="js-preview-link"
href="#md-preview-holder"
tabindex="-1"
- @click.prevent="previewMarkdownTab($event)">
+ @click.prevent="previewMarkdownTab($event)"
+ >
Preview
</a>
</li>
<li
class="md-header-toolbar"
- :class="{ active: !previewMarkdown }">
+ :class="{ active: !previewMarkdown }"
+ >
<toolbar-button
tag="**"
button-title="Add bold text"
- icon="bold" />
+ icon="bold"
+ />
<toolbar-button
tag="*"
button-title="Add italic text"
- icon="italic" />
+ icon="italic"
+ />
<toolbar-button
tag="> "
:prepend="true"
button-title="Insert a quote"
- icon="quote" />
+ icon="quote"
+ />
<toolbar-button
tag="`"
tag-block="```"
button-title="Insert code"
- icon="code" />
+ icon="code"
+ />
<toolbar-button
tag="* "
:prepend="true"
button-title="Add a bullet list"
- icon="list-bulleted" />
+ icon="list-bulleted"
+ />
<toolbar-button
tag="1. "
:prepend="true"
button-title="Add a numbered list"
- icon="list-numbered" />
+ icon="list-numbered"
+ />
<toolbar-button
tag="* [ ] "
:prepend="true"
button-title="Add a task list"
- icon="task-done" />
+ icon="task-done"
+ />
<button
v-tooltip
aria-label="Go full screen"
@@ -115,10 +126,11 @@
data-container="body"
tabindex="-1"
title="Go full screen"
- type="button">
+ type="button"
+ >
<icon
- name="screen-full">
- </icon>
+ name="screen-full"
+ />
</button>
</li>
</ul>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
index ea2509d2839..c0ee88bbf72 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
@@ -8,6 +8,7 @@
quickActionsDocsPath: {
type: String,
required: false,
+ default: '',
},
canAttachFile: {
type: Boolean,
@@ -15,32 +16,40 @@
default: true,
},
},
+ computed: {
+ hasQuickActionsDocsPath() {
+ return this.quickActionsDocsPath !== '';
+ },
+ },
};
</script>
<template>
<div class="comment-toolbar clearfix">
<div class="toolbar-text">
- <template v-if="!quickActionsDocsPath && markdownDocsPath">
+ <template v-if="!hasQuickActionsDocsPath && markdownDocsPath">
<a
:href="markdownDocsPath"
target="_blank"
- tabindex="-1">
+ tabindex="-1"
+ >
Markdown is supported
</a>
</template>
- <template v-if="quickActionsDocsPath && markdownDocsPath">
- <a
+ <template v-if="hasQuickActionsDocsPath && markdownDocsPath">
+ <a
:href="markdownDocsPath"
target="_blank"
- tabindex="-1">
+ tabindex="-1"
+ >
Markdown
</a>
and
- <a
+ <a
:href="quickActionsDocsPath"
target="_blank"
- tabindex="-1">
+ tabindex="-1"
+ >
quick actions
</a>
are supported
@@ -53,46 +62,58 @@
<span class="uploading-progress-container hide">
<i
class="fa fa-file-image-o toolbar-button-icon"
- aria-hidden="true"></i>
+ aria-hidden="true"
+ >
+ </i>
<span class="attaching-file-message"></span>
<span class="uploading-progress">0%</span>
<span class="uploading-spinner">
<i
class="fa fa-spinner fa-spin toolbar-button-icon"
- aria-hidden="true"></i>
+ aria-hidden="true"
+ >
+ </i>
</span>
</span>
<span class="uploading-error-container hide">
<span class="uploading-error-icon">
<i
class="fa fa-file-image-o toolbar-button-icon"
- aria-hidden="true"></i>
+ aria-hidden="true"
+ >
+ </i>
</span>
<span class="uploading-error-message"></span>
<button
class="retry-uploading-link"
- type="button">
- Try again
+ type="button"
+ >
+ Try again
</button>
or
<button
class="attach-new-file markdown-selector"
- type="button">
+ type="button"
+ >
attach a new file
</button>
</span>
<button
class="markdown-selector button-attach-file"
tabindex="-1"
- type="button">
+ type="button"
+ >
<i
class="fa fa-file-image-o toolbar-button-icon"
- aria-hidden="true"></i>
+ aria-hidden="true"
+ >
+ </i>
Attach a file
</button>
<button
class="btn btn-default btn-xs hide button-cancel-uploading-files"
- type="button">
+ type="button"
+ >
Cancel
</button>
</span>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue
index e3e41f8f0ca..2d2d69ebeb2 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue
@@ -3,6 +3,12 @@
import icon from '../icon.vue';
export default {
+ components: {
+ icon,
+ },
+ directives: {
+ tooltip,
+ },
props: {
buttonTitle: {
type: String,
@@ -27,12 +33,6 @@
default: false,
},
},
- components: {
- icon,
- },
- directives: {
- tooltip,
- },
};
</script>
@@ -47,9 +47,10 @@
:data-md-block="tagBlock"
:data-md-prepend="prepend"
:title="buttonTitle"
- :aria-label="buttonTitle">
+ :aria-label="buttonTitle"
+ >
<icon
- :name="icon">
- </icon>
+ :name="icon"
+ />
</button>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/modal.vue b/app/assets/javascripts/vue_shared/components/modal.vue
index 00089dfef38..c103c45c7dd 100644
--- a/app/assets/javascripts/vue_shared/components/modal.vue
+++ b/app/assets/javascripts/vue_shared/components/modal.vue
@@ -1,143 +1,153 @@
<script>
-export default {
- name: 'modal',
+ /* eslint-disable vue/require-default-prop */
+ export default {
+ name: 'Modal',
- props: {
- id: {
- type: String,
- required: false,
+ props: {
+ id: {
+ type: String,
+ required: false,
+ },
+ title: {
+ type: String,
+ required: false,
+ },
+ text: {
+ type: String,
+ required: false,
+ },
+ hideFooter: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ kind: {
+ type: String,
+ required: false,
+ default: 'primary',
+ },
+ modalDialogClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ closeKind: {
+ type: String,
+ required: false,
+ default: 'default',
+ },
+ closeButtonLabel: {
+ type: String,
+ required: false,
+ default: 'Cancel',
+ },
+ primaryButtonLabel: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ submitDisabled: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
- title: {
- type: String,
- required: false,
- },
- text: {
- type: String,
- required: false,
- },
- hideFooter: {
- type: Boolean,
- required: false,
- default: false,
- },
- kind: {
- type: String,
- required: false,
- default: 'primary',
- },
- modalDialogClass: {
- type: String,
- required: false,
- default: '',
- },
- closeKind: {
- type: String,
- required: false,
- default: 'default',
- },
- closeButtonLabel: {
- type: String,
- required: false,
- default: 'Cancel',
- },
- primaryButtonLabel: {
- type: String,
- required: false,
- default: '',
- },
- submitDisabled: {
- type: Boolean,
- required: false,
- default: false,
- },
- },
- computed: {
- btnKindClass() {
- return {
- [`btn-${this.kind}`]: true,
- };
+ computed: {
+ btnKindClass() {
+ return {
+ [`btn-${this.kind}`]: true,
+ };
+ },
+ btnCancelKindClass() {
+ return {
+ [`btn-${this.closeKind}`]: true,
+ };
+ },
},
- btnCancelKindClass() {
- return {
- [`btn-${this.closeKind}`]: true,
- };
- },
- },
- methods: {
- emitCancel(event) {
- this.$emit('cancel', event);
- },
- emitSubmit(event) {
- this.$emit('submit', event);
+ methods: {
+ emitCancel(event) {
+ this.$emit('cancel', event);
+ },
+ emitSubmit(event) {
+ this.$emit('submit', event);
+ },
},
- },
-};
+ };
</script>
<template>
-<div class="modal-open">
- <div
- :id="id"
- class="modal"
- :class="id ? '' : 'show'"
- role="dialog"
- tabindex="-1"
- >
+ <div class="modal-open">
<div
- :class="modalDialogClass"
- class="modal-dialog"
- role="document"
+ :id="id"
+ class="modal"
+ :class="id ? '' : 'show'"
+ role="dialog"
+ tabindex="-1"
>
- <div class="modal-content">
- <div class="modal-header">
- <slot name="header">
- <h4 class="modal-title pull-left">
- {{this.title}}
- </h4>
+ <div
+ :class="modalDialogClass"
+ class="modal-dialog"
+ role="document"
+ >
+ <div class="modal-content">
+ <div class="modal-header">
+ <slot name="header">
+ <h4 class="modal-title pull-left">
+ {{ title }}
+ </h4>
+ <button
+ type="button"
+ class="close pull-right"
+ @click="emitCancel($event)"
+ data-dismiss="modal"
+ aria-label="Close"
+ >
+ <span aria-hidden="true">&times;</span>
+ </button>
+ </slot>
+ </div>
+ <div class="modal-body">
+ <slot
+ name="body"
+ :text="text"
+ >
+ <p>{{ text }}</p>
+ </slot>
+ </div>
+ <div
+ class="modal-footer"
+ v-if="!hideFooter"
+ >
<button
type="button"
- class="close pull-right"
+ class="btn pull-left"
+ :class="btnCancelKindClass"
@click="emitCancel($event)"
data-dismiss="modal"
- aria-label="Close"
>
- <span aria-hidden="true">&times;</span>
+ {{ closeButtonLabel }}
</button>
- </slot>
- </div>
- <div class="modal-body">
- <slot name="body" :text="text">
- <p>{{this.text}}</p>
- </slot>
- </div>
- <div class="modal-footer" v-if="!hideFooter">
- <button
- type="button"
- class="btn pull-left"
- :class="btnCancelKindClass"
- @click="emitCancel($event)"
- data-dismiss="modal">
- {{ closeButtonLabel }}
- </button>
- <button
- v-if="primaryButtonLabel"
- type="button"
- class="btn pull-right js-primary-button"
- :disabled="submitDisabled"
- :class="btnKindClass"
- @click="emitSubmit($event)"
- data-dismiss="modal">
- {{ primaryButtonLabel }}
- </button>
+ <button
+ v-if="primaryButtonLabel"
+ type="button"
+ class="btn pull-right js-primary-button"
+ :disabled="submitDisabled"
+ :class="btnKindClass"
+ @click="emitSubmit($event)"
+ data-dismiss="modal"
+ >
+ {{ primaryButtonLabel }}
+ </button>
+ </div>
</div>
</div>
</div>
+ <div
+ v-if="!id"
+ class="modal-backdrop fade in"
+ >
+ </div>
</div>
- <div
- v-if="!id"
- class="modal-backdrop fade in">
- </div>
-</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/navigation_tabs.vue b/app/assets/javascripts/vue_shared/components/navigation_tabs.vue
index a2ddd565170..cb8e6072a9b 100644
--- a/app/assets/javascripts/vue_shared/components/navigation_tabs.vue
+++ b/app/assets/javascripts/vue_shared/components/navigation_tabs.vue
@@ -45,7 +45,7 @@
this.$emit('onChangeTab', tab.scope);
},
},
-};
+ };
</script>
<template>
<ul class="nav-links scrolling-tabs">
@@ -55,21 +55,20 @@
:class="{
active: tab.isActive,
}"
- >
+ >
<a
role="button"
@click="onTabClick(tab)"
:class="`js-${scope}-tab-${tab.scope}`"
- >
+ >
{{ tab.name }}
<span
v-if="shouldRenderBadge(tab.count)"
class="badge"
- >
- {{tab.count}}
+ >
+ {{ tab.count }}
</span>
-
</a>
</li>
</ul>
diff --git a/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue b/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue
index e467ca56704..50b1508691b 100644
--- a/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue
@@ -20,16 +20,16 @@
import userAvatarLink from '../user_avatar/user_avatar_link.vue';
export default {
- name: 'placeholderNote',
+ name: 'PlaceholderNote',
+ components: {
+ userAvatarLink,
+ },
props: {
note: {
type: Object,
required: true,
},
},
- components: {
- userAvatarLink,
- },
computed: {
...mapGetters([
'getUserData',
@@ -46,7 +46,7 @@
:link-href="getUserData.path"
:img-src="getUserData.avatar_url"
:img-size="40"
- />
+ />
</div>
<div
:class="{ discussion: !note.individual_note }"
@@ -54,14 +54,14 @@
<div class="note-header">
<div class="note-header-info">
<a :href="getUserData.path">
- <span class="hidden-xs">{{getUserData.name}}</span>
- <span class="note-headline-light">@{{getUserData.username}}</span>
+ <span class="hidden-xs">{{ getUserData.name }}</span>
+ <span class="note-headline-light">@{{ getUserData.username }}</span>
</a>
</div>
</div>
<div class="note-body">
<div class="note-text">
- <p>{{note.body}}</p>
+ <p>{{ note.body }}</p>
</div>
</div>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/notes/placeholder_system_note.vue b/app/assets/javascripts/vue_shared/components/notes/placeholder_system_note.vue
index d805fea8006..95e2b38e292 100644
--- a/app/assets/javascripts/vue_shared/components/notes/placeholder_system_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/placeholder_system_note.vue
@@ -8,7 +8,7 @@
* />
*/
export default {
- name: 'placeholderSystemNote',
+ name: 'PlaceholderSystemNote',
props: {
note: {
type: Object,
@@ -20,10 +20,10 @@
<template>
<li class="note system-note timeline-entry being-posted fade-in-half">
- <div class="timeline-entry-inner">
- <div class="timeline-content">
- <em>{{note.body}}</em>
- </div>
- </div>
+ <div class="timeline-entry-inner">
+ <div class="timeline-content">
+ <em>{{ note.body }}</em>
+ </div>
+ </div>
</li>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/notes/system_note.vue b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
index 2248699c399..aac10f84087 100644
--- a/app/assets/javascripts/vue_shared/components/notes/system_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
@@ -21,16 +21,16 @@
import { spriteIcon } from '../../../lib/utils/common_utils';
export default {
- name: 'systemNote',
+ name: 'SystemNote',
+ components: {
+ noteHeader,
+ },
props: {
note: {
type: Object,
required: true,
},
},
- components: {
- noteHeader,
- },
computed: {
...mapGetters([
'targetNoteHash',
diff --git a/app/assets/javascripts/vue_shared/components/panel_resizer.vue b/app/assets/javascripts/vue_shared/components/panel_resizer.vue
index 4371534d345..abbe9a22717 100644
--- a/app/assets/javascripts/vue_shared/components/panel_resizer.vue
+++ b/app/assets/javascripts/vue_shared/components/panel_resizer.vue
@@ -1,87 +1,87 @@
<script>
-export default {
- props: {
- startSize: {
- type: Number,
- required: true,
+ export default {
+ props: {
+ startSize: {
+ type: Number,
+ required: true,
+ },
+ side: {
+ type: String,
+ required: true,
+ },
+ minSize: {
+ type: Number,
+ required: false,
+ default: 0,
+ },
+ maxSize: {
+ type: Number,
+ required: false,
+ default: Number.MAX_VALUE,
+ },
+ enabled: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
},
- side: {
- type: String,
- required: true,
+ data() {
+ return {
+ size: this.startSize,
+ };
},
- minSize: {
- type: Number,
- required: false,
- default: 0,
+ computed: {
+ className() {
+ return `drag${this.side}`;
+ },
+ cursorStyle() {
+ if (this.enabled) {
+ return { cursor: 'ew-resize' };
+ }
+ return {};
+ },
},
- maxSize: {
- type: Number,
- required: false,
- default: Number.MAX_VALUE,
- },
- enabled: {
- type: Boolean,
- required: false,
- default: true,
- },
- },
- data() {
- return {
- size: this.startSize,
- };
- },
- computed: {
- className() {
- return `drag${this.side}`;
- },
- cursorStyle() {
- if (this.enabled) {
- return { cursor: 'ew-resize' };
- }
- return {};
- },
- },
- methods: {
- resetSize(e) {
- e.preventDefault();
- this.size = this.startSize;
- this.$emit('update:size', this.size);
- },
- startDrag(e) {
- if (this.enabled) {
+ methods: {
+ resetSize(e) {
e.preventDefault();
- this.startPos = e.clientX;
- this.currentStartSize = this.size;
- document.addEventListener('mousemove', this.drag);
- document.addEventListener('mouseup', this.endDrag, { once: true });
- this.$emit('resize-start', this.size);
- }
- },
- drag(e) {
- e.preventDefault();
- let moved = e.clientX - this.startPos;
- if (this.side === 'left') moved = -moved;
- let newSize = this.currentStartSize + moved;
- if (newSize < this.minSize) {
- newSize = this.minSize;
- } else if (newSize > this.maxSize) {
- newSize = this.maxSize;
- }
- this.size = newSize;
+ this.size = this.startSize;
+ this.$emit('update:size', this.size);
+ },
+ startDrag(e) {
+ if (this.enabled) {
+ e.preventDefault();
+ this.startPos = e.clientX;
+ this.currentStartSize = this.size;
+ document.addEventListener('mousemove', this.drag);
+ document.addEventListener('mouseup', this.endDrag, { once: true });
+ this.$emit('resize-start', this.size);
+ }
+ },
+ drag(e) {
+ e.preventDefault();
+ let moved = e.clientX - this.startPos;
+ if (this.side === 'left') moved = -moved;
+ let newSize = this.currentStartSize + moved;
+ if (newSize < this.minSize) {
+ newSize = this.minSize;
+ } else if (newSize > this.maxSize) {
+ newSize = this.maxSize;
+ }
+ this.size = newSize;
- this.$emit('update:size', newSize);
- },
- endDrag(e) {
- e.preventDefault();
- document.removeEventListener('mousemove', this.drag);
- this.$emit('resize-end', this.size);
+ this.$emit('update:size', newSize);
+ },
+ endDrag(e) {
+ e.preventDefault();
+ document.removeEventListener('mousemove', this.drag);
+ this.$emit('resize-end', this.size);
+ },
},
- },
-};
+ };
</script>
<template>
- <div
+ <div
class="dragHandle"
:class="className"
:style="cursorStyle"
diff --git a/app/assets/javascripts/vue_shared/components/pikaday.vue b/app/assets/javascripts/vue_shared/components/pikaday.vue
index d8d974a2ff7..bfeece12077 100644
--- a/app/assets/javascripts/vue_shared/components/pikaday.vue
+++ b/app/assets/javascripts/vue_shared/components/pikaday.vue
@@ -3,7 +3,7 @@
import { parsePikadayDate, pikadayToString } from '../../lib/utils/datefix';
export default {
- name: 'datePicker',
+ name: 'DatePicker',
props: {
label: {
type: String,
@@ -13,22 +13,17 @@
selectedDate: {
type: Date,
required: false,
+ default: null,
},
minDate: {
type: Date,
required: false,
+ default: null,
},
maxDate: {
type: Date,
required: false,
- },
- },
- methods: {
- selected(dateText) {
- this.$emit('newDateSelected', this.calendar.toString(dateText));
- },
- toggled() {
- this.$emit('hidePicker');
+ default: null,
},
},
mounted() {
@@ -53,6 +48,14 @@
beforeDestroy() {
this.calendar.destroy();
},
+ methods: {
+ selected(dateText) {
+ this.$emit('newDateSelected', this.calendar.toString(dateText));
+ },
+ toggled() {
+ this.$emit('hidePicker');
+ },
+ },
};
</script>
@@ -66,7 +69,7 @@
@click="toggled"
>
<span class="dropdown-toggle-text">
- {{label}}
+ {{ label }}
</span>
<i
class="fa fa-chevron-down"
diff --git a/app/assets/javascripts/vue_shared/components/project_avatar/image.vue b/app/assets/javascripts/vue_shared/components/project_avatar/image.vue
index dce23bd65f6..279cc1de5bb 100644
--- a/app/assets/javascripts/vue_shared/components/project_avatar/image.vue
+++ b/app/assets/javascripts/vue_shared/components/project_avatar/image.vue
@@ -1,85 +1,85 @@
<script>
-/* This is a re-usable vue component for rendering a project avatar that
- does not need to link to the project's profile. The image and an optional
- tooltip can be configured by props passed to this component.
+ /* This is a re-usable vue component for rendering a project avatar that
+ does not need to link to the project's profile. The image and an optional
+ tooltip can be configured by props passed to this component.
- Sample configuration:
+ Sample configuration:
- <project-avatar-image
- :lazy="true"
- :img-src="projectAvatarSrc"
- :img-alt="tooltipText"
- :tooltip-text="tooltipText"
- tooltip-placement="top"
- />
+ <project-avatar-image
+ :lazy="true"
+ :img-src="projectAvatarSrc"
+ :img-alt="tooltipText"
+ :tooltip-text="tooltipText"
+ tooltip-placement="top"
+ />
-*/
+ */
-import defaultAvatarUrl from 'images/no_avatar.png';
-import { placeholderImage } from '../../../lazy_loader';
-import tooltip from '../../directives/tooltip';
+ import defaultAvatarUrl from 'images/no_avatar.png';
+ import { placeholderImage } from '../../../lazy_loader';
+ import tooltip from '../../directives/tooltip';
-export default {
- name: 'ProjectAvatarImage',
- props: {
- lazy: {
- type: Boolean,
- required: false,
- default: false,
- },
- imgSrc: {
- type: String,
- required: false,
- default: defaultAvatarUrl,
- },
- cssClasses: {
- type: String,
- required: false,
- default: '',
- },
- imgAlt: {
- type: String,
- required: false,
- default: 'project avatar',
- },
- size: {
- type: Number,
- required: false,
- default: 20,
- },
- tooltipText: {
- type: String,
- required: false,
- default: '',
- },
- tooltipPlacement: {
- type: String,
- required: false,
- default: 'top',
- },
- },
- directives: {
- tooltip,
- },
- computed: {
- // API response sends null when gravatar is disabled and
- // we provide an empty string when we use it inside project avatar link.
- // In both cases we should render the defaultAvatarUrl
- sanitizedSource() {
- return this.imgSrc === '' || this.imgSrc === null ? defaultAvatarUrl : this.imgSrc;
- },
- resultantSrcAttribute() {
- return this.lazy ? placeholderImage : this.sanitizedSource;
+ export default {
+ name: 'ProjectAvatarImage',
+ directives: {
+ tooltip,
},
- tooltipContainer() {
- return this.tooltipText ? 'body' : null;
+ props: {
+ lazy: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ imgSrc: {
+ type: String,
+ required: false,
+ default: defaultAvatarUrl,
+ },
+ cssClasses: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ imgAlt: {
+ type: String,
+ required: false,
+ default: 'project avatar',
+ },
+ size: {
+ type: Number,
+ required: false,
+ default: 20,
+ },
+ tooltipText: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ tooltipPlacement: {
+ type: String,
+ required: false,
+ default: 'top',
+ },
},
- avatarSizeClass() {
- return `s${this.size}`;
+ computed: {
+ // API response sends null when gravatar is disabled and
+ // we provide an empty string when we use it inside project avatar link.
+ // In both cases we should render the defaultAvatarUrl
+ sanitizedSource() {
+ return this.imgSrc === '' || this.imgSrc === null ? defaultAvatarUrl : this.imgSrc;
+ },
+ resultantSrcAttribute() {
+ return this.lazy ? placeholderImage : this.sanitizedSource;
+ },
+ tooltipContainer() {
+ return this.tooltipText ? 'body' : null;
+ },
+ avatarSizeClass() {
+ return `s${this.size}`;
+ },
},
- },
-};
+ };
</script>
<template>
@@ -87,7 +87,7 @@ export default {
v-tooltip
class="avatar"
:class="{
- lazy,
+ lazy: lazy,
[avatarSizeClass]: true,
[cssClasses]: true
}"
diff --git a/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue b/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue
index 16d60bb2876..c35621c9ef3 100644
--- a/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue
@@ -1,85 +1,86 @@
<script>
-import modal from './modal.vue';
+ import modal from './modal.vue';
-export default {
- name: 'recaptcha-modal',
+ export default {
+ name: 'RecaptchaModal',
- props: {
- html: {
- type: String,
- required: false,
- default: '',
+ components: {
+ modal,
},
- },
- data() {
- return {
- script: {},
- scriptSrc: 'https://www.google.com/recaptcha/api.js',
- };
- },
+ props: {
+ html: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
- components: {
- modal,
- },
+ data() {
+ return {
+ script: {},
+ scriptSrc: 'https://www.google.com/recaptcha/api.js',
+ };
+ },
- methods: {
- appendRecaptchaScript() {
- this.removeRecaptchaScript();
+ watch: {
+ html() {
+ this.appendRecaptchaScript();
+ },
+ },
- const script = document.createElement('script');
- script.src = this.scriptSrc;
- script.classList.add('js-recaptcha-script');
- script.async = true;
- script.defer = true;
+ mounted() {
+ window.recaptchaDialogCallback = this.submit.bind(this);
+ },
- this.script = script;
+ methods: {
+ appendRecaptchaScript() {
+ this.removeRecaptchaScript();
- document.body.appendChild(script);
- },
+ const script = document.createElement('script');
+ script.src = this.scriptSrc;
+ script.classList.add('js-recaptcha-script');
+ script.async = true;
+ script.defer = true;
- removeRecaptchaScript() {
- if (this.script instanceof Element) this.script.remove();
- },
+ this.script = script;
- close() {
- this.removeRecaptchaScript();
- this.$emit('close');
- },
+ document.body.appendChild(script);
+ },
- submit() {
- this.$el.querySelector('form').submit();
- },
- },
+ removeRecaptchaScript() {
+ if (this.script instanceof Element) this.script.remove();
+ },
- watch: {
- html() {
- this.appendRecaptchaScript();
- },
- },
+ close() {
+ this.removeRecaptchaScript();
+ this.$emit('close');
+ },
- mounted() {
- window.recaptchaDialogCallback = this.submit.bind(this);
- },
-};
+ submit() {
+ this.$el.querySelector('form').submit();
+ },
+ },
+ };
</script>
<template>
-<modal
- kind="warning"
- class="recaptcha-modal js-recaptcha-modal"
- :hide-footer="true"
- :title="__('Please solve the reCAPTCHA')"
- @cancel="close"
->
- <div slot="body">
- <p>
- {{__('We want to be sure it is you, please confirm you are not a robot.')}}
- </p>
- <div
- ref="recaptcha"
- v-html="html"
- ></div>
- </div>
-</modal>
+ <modal
+ kind="warning"
+ class="recaptcha-modal js-recaptcha-modal"
+ :hide-footer="true"
+ :title="__('Please solve the reCAPTCHA')"
+ @cancel="close"
+ >
+ <div slot="body">
+ <p>
+ {{ __('We want to be sure it is you, please confirm you are not a robot.') }}
+ </p>
+ <div
+ ref="recaptcha"
+ v-html="html"
+ >
+ </div>
+ </div>
+ </modal>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue b/app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue
index a88e1310131..7f1eb6bcec4 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue
@@ -1,6 +1,6 @@
<script>
export default {
- name: 'collapsedCalendarIcon',
+ name: 'CollapsedCalendarIcon',
props: {
containerClass: {
type: String,
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue b/app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue
index 9ede5553bc5..dac438a702d 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue
@@ -4,7 +4,11 @@
import collapsedCalendarIcon from './collapsed_calendar_icon.vue';
export default {
- name: 'sidebarCollapsedGroupedDatePicker',
+ name: 'SidebarCollapsedGroupedDatePicker',
+ components: {
+ toggleSidebar,
+ collapsedCalendarIcon,
+ },
props: {
collapsed: {
type: Boolean,
@@ -19,10 +23,12 @@
minDate: {
type: Date,
required: false,
+ default: null,
},
maxDate: {
type: Date,
required: false,
+ default: null,
},
disableClickableIcons: {
type: Boolean,
@@ -30,10 +36,6 @@
default: false,
},
},
- components: {
- toggleSidebar,
- collapsedCalendarIcon,
- },
computed: {
hasMinAndMaxDates() {
return this.minDate && this.maxDate;
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue b/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue
index 9c3413377a3..1413dd69f24 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue
@@ -6,7 +6,13 @@
import { dateInWords } from '../../../lib/utils/datetime_utility';
export default {
- name: 'sidebarDatePicker',
+ name: 'SidebarDatePicker',
+ components: {
+ datePicker,
+ toggleSidebar,
+ loadingIcon,
+ collapsedCalendarIcon,
+ },
props: {
collapsed: {
type: Boolean,
@@ -36,14 +42,17 @@
selectedDate: {
type: Date,
required: false,
+ default: null,
},
minDate: {
type: Date,
required: false,
+ default: null,
},
maxDate: {
type: Date,
required: false,
+ default: null,
},
},
data() {
@@ -51,12 +60,6 @@
editing: false,
};
},
- components: {
- datePicker,
- toggleSidebar,
- loadingIcon,
- collapsedCalendarIcon,
- },
computed: {
selectedAndEditable() {
return this.selectedDate && this.editable;
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue b/app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue
index 5ae76adad71..8211d425b1f 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue
@@ -1,6 +1,6 @@
<script>
export default {
- name: 'toggleSidebar',
+ name: 'ToggleSidebar',
props: {
collapsed: {
type: Boolean,
@@ -24,7 +24,11 @@
<i
aria-label="toggle collapse"
class="fa"
- :class="{ 'fa-angle-double-right': !collapsed, 'fa-angle-double-left': collapsed }"
- ></i>
+ :class="{
+ 'fa-angle-double-right': !collapsed,
+ 'fa-angle-double-left': collapsed
+ }"
+ >
+ </i>
</button>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/table_pagination.vue b/app/assets/javascripts/vue_shared/components/table_pagination.vue
index 33096b53cf8..c44c606a8b2 100644
--- a/app/assets/javascripts/vue_shared/components/table_pagination.vue
+++ b/app/assets/javascripts/vue_shared/components/table_pagination.vue
@@ -1,132 +1,125 @@
<script>
-import { s__ } from '../../locale';
-
-const PAGINATION_UI_BUTTON_LIMIT = 4;
-const UI_LIMIT = 6;
-const SPREAD = '...';
-const PREV = s__('Pagination|Prev');
-const NEXT = s__('Pagination|Next');
-const FIRST = s__('Pagination|« First');
-const LAST = s__('Pagination|Last »');
-
-export default {
- props: {
- /**
- This function will take the information given by the pagination component
-
- Here is an example `change` method:
-
- change(pagenum) {
- gl.utils.visitUrl(`?page=${pagenum}`);
+ import { s__ } from '../../locale';
+
+ const PAGINATION_UI_BUTTON_LIMIT = 4;
+ const UI_LIMIT = 6;
+ const SPREAD = '...';
+ const PREV = s__('Pagination|Prev');
+ const NEXT = s__('Pagination|Next');
+ const FIRST = s__('Pagination|« First');
+ const LAST = s__('Pagination|Last »');
+
+ export default {
+ props: {
+ /**
+ This function will take the information given by the pagination component
+ */
+ change: {
+ type: Function,
+ required: true,
},
- */
- change: {
- type: Function,
- required: true,
- },
- /**
- pageInfo will come from the headers of the API call
- in the `.then` clause of the VueResource API call
- there should be a function that contructs the pageInfo for this component
-
- This is an example:
-
- const pageInfo = headers => ({
- perPage: +headers['X-Per-Page'],
- page: +headers['X-Page'],
- total: +headers['X-Total'],
- totalPages: +headers['X-Total-Pages'],
- nextPage: +headers['X-Next-Page'],
- previousPage: +headers['X-Prev-Page'],
- });
- */
- pageInfo: {
- type: Object,
- required: true,
- },
- },
- methods: {
- changePage(e) {
- if (e.target.parentElement.classList.contains('disabled')) return;
-
- const text = e.target.innerText;
- const { totalPages, nextPage, previousPage } = this.pageInfo;
-
- switch (text) {
- case SPREAD:
- break;
- case LAST:
- this.change(totalPages);
- break;
- case NEXT:
- this.change(nextPage);
- break;
- case PREV:
- this.change(previousPage);
- break;
- case FIRST:
- this.change(1);
- break;
- default:
- this.change(+text);
- break;
- }
- },
- },
- computed: {
- prev() {
- return this.pageInfo.previousPage;
- },
- next() {
- return this.pageInfo.nextPage;
+ /**
+ pageInfo will come from the headers of the API call
+ in the `.then` clause of the VueResource API call
+ there should be a function that contructs the pageInfo for this component
+
+ This is an example:
+
+ const pageInfo = headers => ({
+ perPage: +headers['X-Per-Page'],
+ page: +headers['X-Page'],
+ total: +headers['X-Total'],
+ totalPages: +headers['X-Total-Pages'],
+ nextPage: +headers['X-Next-Page'],
+ previousPage: +headers['X-Prev-Page'],
+ });
+ */
+ pageInfo: {
+ type: Object,
+ required: true,
+ },
},
- getItems() {
- const total = this.pageInfo.totalPages;
- const page = this.pageInfo.page;
- const items = [];
-
- if (page > 1) {
- items.push({ title: FIRST, first: true });
- }
-
- if (page > 1) {
- items.push({ title: PREV, prev: true });
- } else {
- items.push({ title: PREV, disabled: true, prev: true });
- }
-
- if (page > UI_LIMIT) items.push({ title: SPREAD, separator: true });
-
- const start = Math.max(page - PAGINATION_UI_BUTTON_LIMIT, 1);
- const end = Math.min(page + PAGINATION_UI_BUTTON_LIMIT, total);
-
- for (let i = start; i <= end; i += 1) {
- const isActive = i === page;
- items.push({ title: i, active: isActive, page: true });
- }
-
- if (total - page > PAGINATION_UI_BUTTON_LIMIT) {
- items.push({ title: SPREAD, separator: true, page: true });
- }
-
- if (page === total) {
- items.push({ title: NEXT, disabled: true, next: true });
- } else if (total - page >= 1) {
- items.push({ title: NEXT, next: true });
- }
-
- if (total - page >= 1) {
- items.push({ title: LAST, last: true });
- }
-
- return items;
+ computed: {
+ prev() {
+ return this.pageInfo.previousPage;
+ },
+ next() {
+ return this.pageInfo.nextPage;
+ },
+ getItems() {
+ const total = this.pageInfo.totalPages;
+ const page = this.pageInfo.page;
+ const items = [];
+
+ if (page > 1) {
+ items.push({ title: FIRST, first: true });
+ }
+
+ if (page > 1) {
+ items.push({ title: PREV, prev: true });
+ } else {
+ items.push({ title: PREV, disabled: true, prev: true });
+ }
+
+ if (page > UI_LIMIT) items.push({ title: SPREAD, separator: true });
+
+ const start = Math.max(page - PAGINATION_UI_BUTTON_LIMIT, 1);
+ const end = Math.min(page + PAGINATION_UI_BUTTON_LIMIT, total);
+
+ for (let i = start; i <= end; i += 1) {
+ const isActive = i === page;
+ items.push({ title: i, active: isActive, page: true });
+ }
+
+ if (total - page > PAGINATION_UI_BUTTON_LIMIT) {
+ items.push({ title: SPREAD, separator: true, page: true });
+ }
+
+ if (page === total) {
+ items.push({ title: NEXT, disabled: true, next: true });
+ } else if (total - page >= 1) {
+ items.push({ title: NEXT, next: true });
+ }
+
+ if (total - page >= 1) {
+ items.push({ title: LAST, last: true });
+ }
+
+ return items;
+ },
+ showPagination() {
+ return this.pageInfo.totalPages > 1;
+ },
},
- showPagination() {
- return this.pageInfo.totalPages > 1;
+ methods: {
+ changePage(text, isDisabled) {
+ if (isDisabled) return;
+
+ const { totalPages, nextPage, previousPage } = this.pageInfo;
+
+ switch (text) {
+ case SPREAD:
+ break;
+ case LAST:
+ this.change(totalPages);
+ break;
+ case NEXT:
+ this.change(nextPage);
+ break;
+ case PREV:
+ this.change(previousPage);
+ break;
+ case FIRST:
+ this.change(1);
+ break;
+ default:
+ this.change(+text);
+ break;
+ }
+ },
},
- },
-};
+ };
</script>
<template>
<div
@@ -135,7 +128,8 @@ export default {
>
<ul class="pagination clearfix">
<li
- v-for="item in getItems"
+ v-for="(item, index) in getItems"
+ :key="index"
:class="{
page: item.page,
'js-previous-button': item.prev,
@@ -145,8 +139,11 @@ export default {
separator: item.separator,
active: item.active,
disabled: item.disabled
- }">
- <a @click.prevent="changePage($event)">{{item.title}}</a>
+ }"
+ >
+ <a @click.prevent="changePage(item.title, item.disabled)">
+ {{ item.title }}
+ </a>
</li>
</ul>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue b/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue
index 3ff7f6e2c4e..bec4e7c99b6 100644
--- a/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue
+++ b/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue
@@ -8,6 +8,12 @@ import '../../lib/utils/datetime_utility';
*/
export default {
+ directives: {
+ tooltip,
+ },
+ mixins: [
+ timeagoMixin,
+ ],
props: {
time: {
type: String,
@@ -26,14 +32,6 @@ export default {
default: '',
},
},
-
- mixins: [
- timeagoMixin,
- ],
-
- directives: {
- tooltip,
- },
};
</script>
<template>
@@ -43,6 +41,6 @@ export default {
:title="tooltipTitle(time)"
:data-placement="tooltipPlacement"
data-container="body">
- {{timeFormated(time)}}
+ {{ timeFormated(time) }}
</time>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/toggle_button.vue b/app/assets/javascripts/vue_shared/components/toggle_button.vue
index 4277d9281a0..2b12718ae96 100644
--- a/app/assets/javascripts/vue_shared/components/toggle_button.vue
+++ b/app/assets/javascripts/vue_shared/components/toggle_button.vue
@@ -9,6 +9,16 @@
const LABEL_OFF = s__('ToggleButton|Toggle Status: OFF');
export default {
+ components: {
+ icon,
+ loadingIcon,
+ },
+
+ model: {
+ prop: 'value',
+ event: 'change',
+ },
+
props: {
name: {
type: String,
@@ -31,16 +41,6 @@
},
},
- components: {
- icon,
- loadingIcon,
- },
-
- model: {
- prop: 'value',
- event: 'change',
- },
-
computed: {
toggleIcon() {
return this.value ? ICON_ON : ICON_OFF;
diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue
index 1ac61a3c39b..cc9cc46bb4c 100644
--- a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue
+++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue
@@ -22,6 +22,9 @@ import tooltip from '../../directives/tooltip';
export default {
name: 'UserAvatarImage',
+ directives: {
+ tooltip,
+ },
props: {
lazy: {
type: Boolean,
@@ -59,9 +62,6 @@ export default {
default: 'top',
},
},
- directives: {
- tooltip,
- },
computed: {
// API response sends null when gravatar is disabled and
// we provide an empty string when we use it inside user avatar link.
@@ -87,7 +87,7 @@ export default {
v-tooltip
class="avatar"
:class="{
- lazy,
+ lazy: lazy,
[avatarSizeClass]: true,
[cssClasses]: true
}"
diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue
index dc32e783258..6955d164def 100644
--- a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue
+++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue
@@ -26,6 +26,9 @@ export default {
components: {
userAvatarImage,
},
+ directives: {
+ tooltip,
+ },
props: {
linkHref: {
type: String,
@@ -76,9 +79,6 @@ export default {
return this.shouldShowUsername ? '' : this.tooltipText;
},
},
- directives: {
- tooltip,
- },
};
</script>
@@ -98,6 +98,6 @@ export default {
v-tooltip
:title="tooltipText"
:tooltip-placement="tooltipPlacement"
- >{{username}}</span>
+ >{{ username }}</span>
</a>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_svg.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_svg.vue
index d2ff2ac006e..ef3b16edf5f 100644
--- a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_svg.vue
+++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_svg.vue
@@ -39,7 +39,7 @@ export default {
:class="avatarSizeClass"
:height="size"
:width="size"
- v-html="svg">
- </svg>
+ v-html="svg"
+ />
</template>
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index bc907a390d8..d1b3754d4ef 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -28,7 +28,9 @@
.dropdown-menu,
.dropdown-menu-nav {
@include set-visible;
- min-height: 40px;
+ min-height: $dropdown-min-height;
+ max-height: $dropdown-max-height;
+ overflow: auto;
@media (max-width: $screen-xs-max) {
width: 100%;
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 1588036aeae..1e91db5af9b 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -18,14 +18,9 @@
margin: $gl-padding 0;
&.limited-width-container .file-content {
- max-width: $limited-layout-width-sm;
+ max-width: $limited-layout-width;
margin-left: auto;
margin-right: auto;
-
- @media (min-width: $screen-md-min) {
- padding-top: 64px;
- padding-bottom: 64px;
- }
}
}
@@ -128,7 +123,7 @@
}
&.wiki {
- padding: 30px $gl-padding;
+ padding: $gl-padding;
}
&.blob-no-preview {
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index 2d7465401f1..621a4adc0cb 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -260,7 +260,7 @@
}
.filtered-search-input-dropdown-menu {
- max-height: 260px;
+ max-height: $dropdown-max-height;
max-width: 280px;
overflow: auto;
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index f7853909f56..ef1520f1f63 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -334,7 +334,8 @@ $regular_font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-San
* Dropdowns
*/
$dropdown-width: 300px;
-$dropdown-max-height: 215px;
+$dropdown-min-height: 40px;
+$dropdown-max-height: 312px;
$dropdown-vertical-offset: 4px;
$dropdown-link-color: #555;
$dropdown-link-hover-bg: $row-hover;
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 1d081b58f62..7f037582ca0 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -651,12 +651,18 @@
min-width: 0;
}
- .diff-changed-file-name {
+ .diff-changed-file-name,
+ .diff-changed-blank-file-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
+ .diff-changed-blank-file-name {
+ color: $gl-text-color-tertiary;
+ font-style: italic;
+ }
+
.diff-changed-file-path {
color: $gl-text-color-tertiary;
}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index e1637618ab2..ae9a8b0182c 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -303,7 +303,6 @@
.gutter-toggle {
margin-top: 7px;
border-left: 1px solid $border-gray-normal;
- padding-left: 0;
text-align: center;
}
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index dffde736e24..a35ebd48887 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -48,7 +48,7 @@
}
.dropdown-menu {
- max-height: 250px;
+ max-height: $dropdown-max-height;
overflow-y: auto;
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 6f4c678c4b8..61a76d0387a 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -322,13 +322,6 @@
}
}
-.project-repo-buttons {
- .project-action-button .dropdown-menu {
- max-height: 250px;
- overflow-y: auto;
- }
-}
-
.split-one {
display: inline-table;
margin-right: 12px;
diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss
index 6cb32408a48..acbd9936706 100644
--- a/app/assets/stylesheets/pages/repo.scss
+++ b/app/assets/stylesheets/pages/repo.scss
@@ -16,12 +16,6 @@
display: inline-block;
}
-@media (min-width: $screen-md-min) {
- .blob-viewer[data-type="rich"] {
- margin: 20px;
- }
-}
-
.ide-view {
display: flex;
height: calc(100vh - #{$header-height});
diff --git a/app/controllers/projects/clusters/gcp_controller.rb b/app/controllers/projects/clusters/gcp_controller.rb
index 25608df0b9c..4fc515bd03e 100644
--- a/app/controllers/projects/clusters/gcp_controller.rb
+++ b/app/controllers/projects/clusters/gcp_controller.rb
@@ -1,8 +1,9 @@
class Projects::Clusters::GcpController < Projects::ApplicationController
before_action :authorize_read_cluster!
before_action :authorize_google_api, except: [:login]
- before_action :authorize_google_project_billing, only: [:new]
+ before_action :authorize_google_project_billing, only: [:new, :create]
before_action :authorize_create_cluster!, only: [:new, :create]
+ before_action :verify_billing, only: [:create]
def login
begin
@@ -23,24 +24,34 @@ class Projects::Clusters::GcpController < Projects::ApplicationController
end
def create
+ @cluster = ::Clusters::CreateService
+ .new(project, current_user, create_params)
+ .execute(token_in_session)
+
+ if @cluster.persisted?
+ redirect_to project_cluster_path(project, @cluster)
+ else
+ render :new
+ end
+ end
+
+ private
+
+ def verify_billing
case google_project_billing_status
when 'true'
- @cluster = ::Clusters::CreateService
- .new(project, current_user, create_params)
- .execute(token_in_session)
-
- return redirect_to project_cluster_path(project, @cluster) if @cluster.persisted?
+ return
when 'false'
- flash[:error] = _('Please enable billing for one of your projects to be able to create a cluster.')
+ flash[:alert] = _('Please <a href=%{link_to_billing} target="_blank" rel="noopener noreferrer">enable billing for one of your projects to be able to create a cluster</a>, then try again.').html_safe % { link_to_billing: "https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral" }
else
- flash[:error] = _('We could not verify that one of your projects on GCP has billing enabled. Please try again.')
+ flash[:alert] = _('We could not verify that one of your projects on GCP has billing enabled. Please try again.')
end
+ @cluster = ::Clusters::Cluster.new(create_params)
+
render :new
end
- private
-
def create_params
params.require(:cluster).permit(
:enabled,
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index f9dcb32f7c4..5e3b2e5581c 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -46,7 +46,7 @@ module BlobHelper
end
def ide_edit_text
- "#{_('Multi Edit')} <span class='label label-primary'>#{_('Beta')}</span>".html_safe
+ "#{_('Web IDE')}"
end
def ide_blob_link(project = @project, ref = @ref, path = @path, options = {})
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index e35de9b97ee..afab72930c1 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -49,6 +49,7 @@ class MergeRequestDiff < ActiveRecord::Base
ensure_commit_shas
save_commits
save_diffs
+ save
keep_around_commits
end
@@ -56,7 +57,6 @@ class MergeRequestDiff < ActiveRecord::Base
self.start_commit_sha ||= merge_request.target_branch_sha
self.head_commit_sha ||= merge_request.source_branch_sha
self.base_commit_sha ||= find_base_sha
- save
end
# Override head_commit_sha to keep compatibility with merge request diff
@@ -195,7 +195,7 @@ class MergeRequestDiff < ActiveRecord::Base
end
def commits_count
- merge_request_diff_commits.size
+ super || merge_request_diff_commits.size
end
private
@@ -264,13 +264,16 @@ class MergeRequestDiff < ActiveRecord::Base
new_attributes[:state] = :overflow if diff_collection.overflow?
end
- update(new_attributes)
+ assign_attributes(new_attributes)
end
def save_commits
MergeRequestDiffCommit.create_bulk(self.id, compare.commits.reverse)
- merge_request_diff_commits.reload
+ # merge_request_diff_commits.reload is preferred way to reload associated
+ # objects but it returns cached result for some reason in this case
+ commits = merge_request_diff_commits(true)
+ self.commits_count = commits.size
end
def repository
diff --git a/app/models/project.rb b/app/models/project.rb
index fbe65e700a4..7dc5e980c1b 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -1149,7 +1149,7 @@ class Project < ActiveRecord::Base
def change_head(branch)
if repository.branch_exists?(branch)
repository.before_change_head
- repository.write_ref('HEAD', "refs/heads/#{branch}")
+ repository.raw_repository.write_ref('HEAD', "refs/heads/#{branch}", shell: false)
repository.copy_gitattributes(branch)
repository.after_change_head
reload_default_branch
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 9c879e2006b..a84d6a1426b 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -103,6 +103,10 @@ class Repository
"#<#{self.class.name}:#{@disk_path}>"
end
+ def create_hooks
+ Gitlab::Git::Repository.create_hooks(path_to_repo, Gitlab.config.gitlab_shell.hooks_path)
+ end
+
def commit(ref = 'HEAD')
return nil unless exists?
return ref if ref.is_a?(::Commit)
@@ -256,7 +260,7 @@ class Repository
# This will still fail if the file is corrupted (e.g. 0 bytes)
begin
- write_ref(keep_around_ref_name(sha), sha)
+ raw_repository.write_ref(keep_around_ref_name(sha), sha, shell: false)
rescue Rugged::ReferenceError => ex
Rails.logger.error "Unable to create #{REF_KEEP_AROUND} reference for repository #{path}: #{ex}"
rescue Rugged::OSError => ex
@@ -270,10 +274,6 @@ class Repository
ref_exists?(keep_around_ref_name(sha))
end
- def write_ref(ref_path, sha)
- rugged.references.create(ref_path, sha, force: true)
- end
-
def diverging_commit_counts(branch)
root_ref_hash = raw_repository.commit(root_ref).id
cache.fetch(:"diverging_commit_counts_#{branch.name}") do
diff --git a/app/services/check_gcp_project_billing_service.rb b/app/services/check_gcp_project_billing_service.rb
index 854adf2177d..ea82b61b279 100644
--- a/app/services/check_gcp_project_billing_service.rb
+++ b/app/services/check_gcp_project_billing_service.rb
@@ -2,7 +2,10 @@ class CheckGcpProjectBillingService
def execute(token)
client = GoogleApi::CloudPlatform::Client.new(token, nil)
client.projects_list.select do |project|
- client.projects_get_billing_info(project.name).billingEnabled
+ begin
+ client.projects_get_billing_info(project.project_id).billing_enabled
+ rescue
+ end
end
end
end
diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb
index 29def25719d..2f511ab44b7 100644
--- a/app/services/issues/move_service.rb
+++ b/app/services/issues/move_service.rb
@@ -24,7 +24,7 @@ module Issues
@new_issue = create_new_issue
rewrite_notes
- rewrite_award_emoji
+ rewrite_issue_award_emoji
add_note_moved_from
# Old issue tasks
@@ -76,7 +76,7 @@ module Issues
end
def rewrite_notes
- @old_issue.notes.find_each do |note|
+ @old_issue.notes_with_associations.find_each do |note|
new_note = note.dup
new_params = { project: @new_project, noteable: @new_issue,
note: rewrite_content(new_note.note),
@@ -84,13 +84,19 @@ module Issues
updated_at: note.updated_at }
new_note.update(new_params)
+
+ rewrite_award_emoji(note, new_note)
end
end
- def rewrite_award_emoji
- @old_issue.award_emoji.each do |award|
+ def rewrite_issue_award_emoji
+ rewrite_award_emoji(@old_issue, @new_issue)
+ end
+
+ def rewrite_award_emoji(old_awardable, new_awardable)
+ old_awardable.award_emoji.each do |award|
new_award = award.dup
- new_award.awardable = @new_issue
+ new_award.awardable = new_awardable
new_award.save
end
end
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index 9622a5c5462..22b9b91a957 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -154,13 +154,9 @@ module MergeRequests
end
def assign_title_from_issue
- return unless issue
+ return unless issue && issue.is_a?(Issue)
- merge_request.title =
- case issue
- when Issue then "Resolve \"#{issue.title}\""
- when ExternalIssue then "Resolve #{issue.title}"
- end
+ merge_request.title = "Resolve \"#{issue.title}\""
end
def issue_iid
diff --git a/app/services/merge_requests/rebase_service.rb b/app/services/merge_requests/rebase_service.rb
index 0d5a25fa28e..c0083cd6afd 100644
--- a/app/services/merge_requests/rebase_service.rb
+++ b/app/services/merge_requests/rebase_service.rb
@@ -1,12 +1,14 @@
module MergeRequests
class RebaseService < MergeRequests::WorkingCopyBaseService
+ REBASE_ERROR = 'Rebase failed. Please rebase locally'.freeze
+
def execute(merge_request)
@merge_request = merge_request
if rebase
success
else
- error('Failed to rebase. Should be done manually')
+ error(REBASE_ERROR)
end
end
@@ -22,8 +24,8 @@ module MergeRequests
true
rescue => e
- log_error('Failed to rebase branch:')
- log_error(e.message, save_message_on_model: true)
+ log_error(REBASE_ERROR, save_message_on_model: true)
+ log_error(e.message)
false
end
end
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 39eb71c2bac..46727811be4 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -1,4 +1,4 @@
-%header.navbar.navbar-gitlab
+%header.navbar.navbar-gitlab.qa-navbar
%a.sr-only.gl-accessibility{ href: "#content-body", tabindex: "1" } Skip to content
.container-fluid
.header-content
@@ -43,7 +43,7 @@
= todos_count_format(todos_pending_count)
%li.header-user.dropdown
= link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do
- = image_tag avatar_icon(current_user, 23), width: 23, height: 23, class: "header-user-avatar"
+ = image_tag avatar_icon(current_user, 23), width: 23, height: 23, class: "header-user-avatar qa-user-avatar"
= sprite_icon('angle-down', css_class: 'caret-down')
.dropdown-menu-nav.dropdown-menu-align-right
%ul
@@ -56,8 +56,6 @@
= link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username }
%li
= link_to "Settings", profile_path
- %li
- = link_to "Turn on multi edit", profile_preferences_path
- if current_user
%li
= link_to "Help", help_path
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 4013181da9c..74532eba298 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -1,5 +1,5 @@
%ul.list-unstyled.navbar-sub-nav
- = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: { id: 'nav-projects-dropdown', class: "home dropdown header-projects" }) do
+ = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: { id: 'nav-projects-dropdown', class: "home dropdown header-projects qa-projects-dropdown" }) do
%a{ href: "#", data: { toggle: "dropdown" } }
Projects
= sprite_icon('angle-down', css_class: 'caret-down')
@@ -7,7 +7,7 @@
= render "layouts/nav/projects_dropdown/show"
= nav_link(controller: ['dashboard/groups', 'explore/groups'], html_options: { class: "hidden-xs" }) do
- = link_to dashboard_groups_path, class: 'dashboard-shortcuts-groups', title: 'Groups' do
+ = link_to dashboard_groups_path, class: 'dashboard-shortcuts-groups qa-groups-link', title: 'Groups' do
Groups
= nav_link(path: 'dashboard#activity', html_options: { class: "visible-lg" }) do
@@ -59,7 +59,7 @@
%li.line-separator.hidden-xs
- if current_user.admin?
= nav_link(controller: 'admin/dashboard') do
- = link_to admin_root_path, class: 'admin-icon', title: 'Admin area', aria: { label: "Admin area" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = link_to admin_root_path, class: 'admin-icon qa-admin-area-link', title: 'Admin area', aria: { label: "Admin area" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= sprite_icon('admin', size: 18)
- if Gitlab::Sherlock.enabled?
%li
diff --git a/app/views/layouts/nav/projects_dropdown/_show.html.haml b/app/views/layouts/nav/projects_dropdown/_show.html.haml
index 32a24c101fc..59becb043d3 100644
--- a/app/views/layouts/nav/projects_dropdown/_show.html.haml
+++ b/app/views/layouts/nav/projects_dropdown/_show.html.haml
@@ -1,9 +1,9 @@
- project_meta = { id: @project.id, name: @project.name, namespace: @project.name_with_namespace, web_url: project_path(@project), avatar_url: @project.avatar_url } if @project&.persisted?
.projects-dropdown-container
- .project-dropdown-sidebar
+ .project-dropdown-sidebar.qa-projects-dropdown-sidebar
%ul
= nav_link(path: 'dashboard/projects#index') do
- = link_to dashboard_projects_path do
+ = link_to dashboard_projects_path, class: 'qa-your-projects-link' do
= _('Your projects')
= nav_link(path: 'projects#starred') do
= link_to starred_dashboard_projects_path do
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index 1fa3a3041fd..abd07d71bcc 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -226,7 +226,7 @@
= link_to edit_project_path(@project), class: 'shortcuts-tree' do
.nav-icon-container
= sprite_icon('settings')
- %span.nav-item-name
+ %span.nav-item-name.qa-settings-item
Settings
%ul.sidebar-sub-level-items
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index 65328791ce5..aeae7455a1c 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -5,8 +5,8 @@
= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: { class: 'row prepend-top-default js-preferences-form' } do |f|
.col-lg-4
%h4.prepend-top-0
- GitLab multi file editor
- %p Unlock an additional editing experience which makes it possible to edit and commit multiple files
+ Web IDE (Beta)
+ %p Enable the new web IDE on this device to make it possible to open and edit multiple files with a single commit
.col-lg-8.multi-file-editor-options
= label_tag do
.preview.append-bottom-10= image_tag "multi-editor-off.png"
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 1d644dda177..b565f14747a 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -4,7 +4,7 @@
.limit-container-width{ class: container_class }
.avatar-container.s70.project-avatar
= project_icon(@project, alt: @project.name, class: 'avatar s70 avatar-tile')
- %h1.project-title
+ %h1.project-title.qa-project-name
= @project.name
%span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@project) }
= visibility_level_icon(@project.visibility_level, fw: false)
diff --git a/app/views/projects/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml
index a78a8e5d628..bd99eb93cc8 100644
--- a/app/views/projects/_new_project_fields.html.haml
+++ b/app/views/projects/_new_project_fields.html.haml
@@ -9,7 +9,7 @@
- if current_user.can_select_namespace?
.input-group-addon
= root_url
- = f.select :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), {}, { class: 'select2 js-select-namespace', tabindex: 1}
+ = f.select :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), {}, { class: 'select2 js-select-namespace qa-project-namespace-select', tabindex: 1}
- else
.input-group-addon.static-namespace
diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml
index 2589c53beae..8e8c911185a 100644
--- a/app/views/projects/buttons/_dropdown.html.haml
+++ b/app/views/projects/buttons/_dropdown.html.haml
@@ -30,12 +30,13 @@
%li
= link_to project_new_blob_path(@project, @project.default_branch || 'master') do
#{ _('New file') }
- %li
- = link_to new_project_branch_path(@project) do
- #{ _('New branch') }
- %li
- = link_to new_project_tag_path(@project) do
- #{ _('New tag') }
+ - unless @project.empty_repo?
+ %li
+ = link_to new_project_branch_path(@project) do
+ #{ _('New branch') }
+ %li
+ = link_to new_project_tag_path(@project) do
+ #{ _('New tag') }
- elsif current_user && current_user.already_forked?(@project)
%li
= link_to project_new_blob_path(@project, @project.default_branch || 'master') do
diff --git a/app/views/projects/clusters/gcp/_header.html.haml b/app/views/projects/clusters/gcp/_header.html.haml
index e2d7326a312..bddb902115d 100644
--- a/app/views/projects/clusters/gcp/_header.html.haml
+++ b/app/views/projects/clusters/gcp/_header.html.haml
@@ -4,11 +4,11 @@
= s_('ClusterIntegration|Please make sure that your Google account meets the following requirements:')
%ul
%li
- - link_to_kubernetes_engine = link_to(s_('ClusterIntegration|access to Google Kubernetes Engine'), 'https://console.cloud.google.com', target: '_blank', rel: 'noopener noreferrer')
+ - link_to_kubernetes_engine = link_to(s_('ClusterIntegration|access to Google Kubernetes Engine'), 'https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral', target: '_blank', rel: 'noopener noreferrer')
= s_('ClusterIntegration|Your account must have %{link_to_kubernetes_engine}').html_safe % { link_to_kubernetes_engine: link_to_kubernetes_engine }
%li
- - link_to_requirements = link_to(s_('ClusterIntegration|meets the requirements'), 'https://cloud.google.com/kubernetes-engine/docs/quickstart', target: '_blank', rel: 'noopener noreferrer')
+ - link_to_requirements = link_to(s_('ClusterIntegration|meets the requirements'), 'https://cloud.google.com/kubernetes-engine/docs/quickstart?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral', target: '_blank', rel: 'noopener noreferrer')
= s_('ClusterIntegration|Make sure your account %{link_to_requirements} to create clusters').html_safe % { link_to_requirements: link_to_requirements }
%li
- - link_to_container_project = link_to(s_('ClusterIntegration|Google Kubernetes Engine project'), 'https://console.cloud.google.com/home/dashboard', target: '_blank', rel: 'noopener noreferrer')
+ - link_to_container_project = link_to(s_('ClusterIntegration|Google Kubernetes Engine project'), 'https://console.cloud.google.com/home/dashboard?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral', target: '_blank', rel: 'noopener noreferrer')
= s_('ClusterIntegration|This account must have permissions to create a cluster in the %{link_to_container_project} specified below').html_safe % { link_to_container_project: link_to_container_project }
diff --git a/app/views/projects/clusters/show.html.haml b/app/views/projects/clusters/show.html.haml
index c7c84b5a42c..2049105dff6 100644
--- a/app/views/projects/clusters/show.html.haml
+++ b/app/views/projects/clusters/show.html.haml
@@ -1,6 +1,6 @@
- @content_class = "limit-container-width" unless fluid_layout
- add_to_breadcrumbs "Clusters", project_clusters_path(@project)
-- breadcrumb_title @cluster.id
+- breadcrumb_title @cluster.name
- page_title _("Cluster")
- expanded = Rails.env.test?
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index adc4dcbed33..0b01e38d23d 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -11,7 +11,7 @@
- unless diff_file.submodule?
- blob = diff_file.blob
.file-actions.hidden-xs
- - if blob.readable_text?
+ - if blob&.readable_text?
= link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip', title: "Toggle comments for this file", disabled: @diff_notes_disabled do
= icon('comment')
\
diff --git a/app/views/projects/diffs/_file_header.html.haml b/app/views/projects/diffs/_file_header.html.haml
index 73c316472e3..dbeddf6689a 100644
--- a/app/views/projects/diffs/_file_header.html.haml
+++ b/app/views/projects/diffs/_file_header.html.haml
@@ -35,3 +35,6 @@
- if diff_file.mode_changed?
%small
#{diff_file.a_mode} → #{diff_file.b_mode}
+
+ - if diff_file.stored_externally? && diff_file.external_storage == :lfs
+ %span.label.label-lfs.append-right-5 LFS
diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml
index 325159dd9a7..b082ad0ef0e 100644
--- a/app/views/projects/diffs/_stats.html.haml
+++ b/app/views/projects/diffs/_stats.html.haml
@@ -24,7 +24,12 @@
%a.diff-changed-file{ href: "##{hexdigest(diff_file.file_path)}", title: diff_file.new_path }
= sprite_icon(diff_file_changed_icon(diff_file), size: 16, css_class: "#{diff_file_changed_icon_color(diff_file)} diff-file-changed-icon append-right-8")
%span.diff-changed-file-content.append-right-8
- %strong.diff-changed-file-name= diff_file.blob.name
+ - if diff_file.blob&.name
+ %strong.diff-changed-file-name
+ = diff_file.blob.name
+ - else
+ %strong.diff-changed-blank-file-name
+ = s_('Diffs|No file name available')
%span.diff-changed-file-path.prepend-top-5= diff_file_path_text(diff_file)
%span.diff-changed-stats
%span.cgreen<
diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml
index 1cba4fc6c41..687cd4d1532 100644
--- a/app/views/shared/_clone_panel.html.haml
+++ b/app/views/shared/_clone_panel.html.haml
@@ -7,7 +7,7 @@
%span
= enabled_project_button(project, enabled_protocol)
- else
- %a#clone-dropdown.btn.clone-dropdown-btn{ href: '#', data: { toggle: 'dropdown' } }
+ %a#clone-dropdown.btn.clone-dropdown-btn.qa-clone-dropdown{ href: '#', data: { toggle: 'dropdown' } }
%span
= default_clone_protocol.upcase
= icon('caret-down')
diff --git a/app/workers/check_gcp_project_billing_worker.rb b/app/workers/check_gcp_project_billing_worker.rb
index 557af14ee57..5466ccdda59 100644
--- a/app/workers/check_gcp_project_billing_worker.rb
+++ b/app/workers/check_gcp_project_billing_worker.rb
@@ -4,7 +4,7 @@ class CheckGcpProjectBillingWorker
include ApplicationWorker
include ClusterQueue
- LEASE_TIMEOUT = 15.seconds.to_i
+ LEASE_TIMEOUT = 3.seconds.to_i
SESSION_KEY_TIMEOUT = 5.minutes
BILLING_TIMEOUT = 1.hour
@@ -23,13 +23,13 @@ class CheckGcpProjectBillingWorker
end
def self.redis_shared_state_key_for(token)
- "gitlab:gcp:#{token.hash}:billing_enabled"
+ "gitlab:gcp:#{Digest::SHA1.hexdigest(token)}:billing_enabled"
end
def perform(token_key)
return unless token_key
- token = self.get_session_token(token_key)
+ token = self.class.get_session_token(token_key)
return unless token
return unless try_obtain_lease_for(token)
diff --git a/changelogs/unreleased/36669-default-mr-title-with-external-issues.yml b/changelogs/unreleased/36669-default-mr-title-with-external-issues.yml
new file mode 100644
index 00000000000..6af9ac4b099
--- /dev/null
+++ b/changelogs/unreleased/36669-default-mr-title-with-external-issues.yml
@@ -0,0 +1,5 @@
+---
+title: Default merge request title is set correctly again when external issue tracker is activated
+merge_request: 16356
+author: Ben305
+type: fixed
diff --git a/changelogs/unreleased/38068-commits-count.yml b/changelogs/unreleased/38068-commits-count.yml
new file mode 100644
index 00000000000..3fbf554c98c
--- /dev/null
+++ b/changelogs/unreleased/38068-commits-count.yml
@@ -0,0 +1,5 @@
+---
+title: Store number of commits in merge_request_diffs table.
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/39214__pipeline_api.yml b/changelogs/unreleased/39214__pipeline_api.yml
new file mode 100644
index 00000000000..18ee2e43798
--- /dev/null
+++ b/changelogs/unreleased/39214__pipeline_api.yml
@@ -0,0 +1,5 @@
+---
+title: Add `pipelines` endpoint to merge requests API
+merge_request: 15454
+author: Tony Rom <thetonyrom@gmail.com>
+type: added
diff --git a/changelogs/unreleased/39988-hide-new-branch-tag-empty-repo.yml b/changelogs/unreleased/39988-hide-new-branch-tag-empty-repo.yml
new file mode 100644
index 00000000000..4f2c87c44b3
--- /dev/null
+++ b/changelogs/unreleased/39988-hide-new-branch-tag-empty-repo.yml
@@ -0,0 +1,5 @@
+---
+title: Hide new branch and tag links for projects with an empty repo
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/4020-rebase-message.yml b/changelogs/unreleased/4020-rebase-message.yml
new file mode 100644
index 00000000000..4793f3d9cb9
--- /dev/null
+++ b/changelogs/unreleased/4020-rebase-message.yml
@@ -0,0 +1,5 @@
+---
+title: Display user friendly error message if rebase fails.
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/41491-fix-nil-blob-name-error.yml b/changelogs/unreleased/41491-fix-nil-blob-name-error.yml
new file mode 100644
index 00000000000..cf7e63ea46a
--- /dev/null
+++ b/changelogs/unreleased/41491-fix-nil-blob-name-error.yml
@@ -0,0 +1,5 @@
+---
+title: Fix 500 error when visiting a commit where the blobs do not exist
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/41600-wider-project-readme-on-fixed-layout.yml b/changelogs/unreleased/41600-wider-project-readme-on-fixed-layout.yml
new file mode 100644
index 00000000000..e50f6046b17
--- /dev/null
+++ b/changelogs/unreleased/41600-wider-project-readme-on-fixed-layout.yml
@@ -0,0 +1,5 @@
+---
+title: Make project README containers wider on fixed layout
+merge_request: 16181
+author: Takuya Noguchi
+type: fixed
diff --git a/changelogs/unreleased/41613-fix-redundant-modal.yml b/changelogs/unreleased/41613-fix-redundant-modal.yml
new file mode 100644
index 00000000000..9e157b3065a
--- /dev/null
+++ b/changelogs/unreleased/41613-fix-redundant-modal.yml
@@ -0,0 +1,5 @@
+---
+title: Make modal dialog common for Groups tree app
+merge_request: 16311
+author:
+type: fixed
diff --git a/changelogs/unreleased/41709-rich-blob-viewer-margins-for-pc.yml b/changelogs/unreleased/41709-rich-blob-viewer-margins-for-pc.yml
new file mode 100644
index 00000000000..51285e5476f
--- /dev/null
+++ b/changelogs/unreleased/41709-rich-blob-viewer-margins-for-pc.yml
@@ -0,0 +1,5 @@
+---
+title: Make rich blob viewer wider for PC
+merge_request: 16262
+author: Takuya Noguchi
+type: fixed
diff --git a/changelogs/unreleased/41789-fix-up-web-ide-user-preference-copy-and-buttons.yml b/changelogs/unreleased/41789-fix-up-web-ide-user-preference-copy-and-buttons.yml
new file mode 100644
index 00000000000..fe87cd5cadb
--- /dev/null
+++ b/changelogs/unreleased/41789-fix-up-web-ide-user-preference-copy-and-buttons.yml
@@ -0,0 +1,5 @@
+---
+title: Fix web ide user preferences copy and buttons
+merge_request: 41789
+author:
+type: other
diff --git a/changelogs/unreleased/da-verify-integrity-of-uploaded-files.yml b/changelogs/unreleased/da-verify-integrity-of-uploaded-files.yml
new file mode 100644
index 00000000000..5b850c92d17
--- /dev/null
+++ b/changelogs/unreleased/da-verify-integrity-of-uploaded-files.yml
@@ -0,0 +1,5 @@
+---
+title: Add rake task to check integrity of uploaded files
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/fj-41477-fix-bug-wiki-last-version.yml b/changelogs/unreleased/fj-41477-fix-bug-wiki-last-version.yml
new file mode 100644
index 00000000000..e4b1343876a
--- /dev/null
+++ b/changelogs/unreleased/fj-41477-fix-bug-wiki-last-version.yml
@@ -0,0 +1,5 @@
+---
+title: Fixing bug when wiki last version
+merge_request: 16197
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-award-emoji-move-issues.yml b/changelogs/unreleased/sh-fix-award-emoji-move-issues.yml
new file mode 100644
index 00000000000..c62fad927d0
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-award-emoji-move-issues.yml
@@ -0,0 +1,5 @@
+---
+title: Fix bug where award emojis would be lost when moving issues between projects
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-bare-import-hooks.yml b/changelogs/unreleased/sh-fix-bare-import-hooks.yml
new file mode 100644
index 00000000000..deb6c62f738
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-bare-import-hooks.yml
@@ -0,0 +1,5 @@
+---
+title: Fix hooks not being set up properly for bare import Rake task
+merge_request:
+author:
+type: fixed
diff --git a/config/initializers/gollum.rb b/config/initializers/gollum.rb
index f1066f83dd9..0b86cac51a7 100644
--- a/config/initializers/gollum.rb
+++ b/config/initializers/gollum.rb
@@ -36,6 +36,26 @@ module Gollum
end
end
end
+
+ module Git
+ class Git
+ def tree_entry(commit, path)
+ pathname = Pathname.new(path)
+ tmp_entry = nil
+
+ pathname.each_filename do |dir|
+ tmp_entry = if tmp_entry.nil?
+ commit.tree[dir]
+ else
+ @repo.lookup(tmp_entry[:oid])[dir]
+ end
+
+ return nil unless tmp_entry
+ end
+ tmp_entry
+ end
+ end
+ end
end
Rails.application.configure do
diff --git a/db/migrate/20180105212544_add_commits_count_to_merge_request_diff.rb b/db/migrate/20180105212544_add_commits_count_to_merge_request_diff.rb
new file mode 100644
index 00000000000..f942b4c062e
--- /dev/null
+++ b/db/migrate/20180105212544_add_commits_count_to_merge_request_diff.rb
@@ -0,0 +1,29 @@
+class AddCommitsCountToMergeRequestDiff < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ MIGRATION = 'AddMergeRequestDiffCommitsCount'.freeze
+ BATCH_SIZE = 5000
+ DELAY_INTERVAL = 5.minutes.to_i
+
+ class MergeRequestDiff < ActiveRecord::Base
+ self.table_name = 'merge_request_diffs'
+
+ include ::EachBatch
+ end
+
+ disable_ddl_transaction!
+
+ def up
+ add_column :merge_request_diffs, :commits_count, :integer
+
+ say 'Populating the MergeRequestDiff `commits_count`'
+
+ queue_background_migration_jobs_by_range_at_intervals(MergeRequestDiff, MIGRATION, DELAY_INTERVAL, batch_size: BATCH_SIZE)
+ end
+
+ def down
+ remove_column :merge_request_diffs, :commits_count
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index a16f756ccfb..07620349057 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20171230123729) do
+ActiveRecord::Schema.define(version: 20180105212544) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -1044,6 +1044,7 @@ ActiveRecord::Schema.define(version: 20171230123729) do
t.string "real_size"
t.string "head_commit_sha"
t.string "start_commit_sha"
+ t.integer "commits_count"
end
add_index "merge_request_diffs", ["merge_request_id", "id"], name: "index_merge_request_diffs_on_merge_request_id_and_id", using: :btree
diff --git a/doc/administration/auth/crowd.md b/doc/administration/auth/crowd.md
index 2c289c67a6d..6db74958d5a 100644
--- a/doc/administration/auth/crowd.md
+++ b/doc/administration/auth/crowd.md
@@ -66,3 +66,15 @@ On the sign in page there should now be a Crowd tab in the sign in form.
[reconfigure]: ../restart_gitlab.md#omnibus-gitlab-reconfigure
[restart]: ../restart_gitlab.md#installations-from-source
+
+## Troubleshooting
+
+If you see an error message like the one below when you sign in after Crowd authentication is configured, you may want to consult the Crowd administrator for the Crowd log file to know the exact cause:
+
+```
+could not authorize you from Crowd because invalid credentials
+```
+
+Please make sure the Crowd users who need to login to GitLab are authorized to [the application](#configure-a-new-crowd-application) in the step of **Authorisation**. This could be verified by try "Authentication test" for Crowd as of 2.11.
+
+![Example Crowd application authorisation configuration](img/crowd_application_authorisation.png) \ No newline at end of file
diff --git a/doc/administration/auth/img/crowd_application_authorisation.png b/doc/administration/auth/img/crowd_application_authorisation.png
new file mode 100644
index 00000000000..70339891b34
--- /dev/null
+++ b/doc/administration/auth/img/crowd_application_authorisation.png
Binary files differ
diff --git a/doc/administration/operations/fast_ssh_key_lookup.md b/doc/administration/operations/fast_ssh_key_lookup.md
index b86168f935a..835ed8c8006 100644
--- a/doc/administration/operations/fast_ssh_key_lookup.md
+++ b/doc/administration/operations/fast_ssh_key_lookup.md
@@ -25,34 +25,12 @@ GitLab Shell provides a way to authorize SSH users via a fast, indexed lookup
to the GitLab database. GitLab Shell uses the fingerprint of the SSH key to
check whether the user is authorized to access GitLab.
-Create the directory `/opt/gitlab-shell` first:
-
-```bash
-sudo mkdir -p /opt/gitlab-shell
-```
-
-Create this file at `/opt/gitlab-shell/authorized_keys`:
-
-```
-#!/bin/bash
-
-if [[ "$1" == "git" ]]; then
- /opt/gitlab/embedded/service/gitlab-shell/bin/authorized_keys $2
-fi
-```
-
-Set appropriate ownership and permissions:
-
-```
-sudo chown root:git /opt/gitlab-shell/authorized_keys
-sudo chmod 0650 /opt/gitlab-shell/authorized_keys
-```
-
-Add the following to `/etc/ssh/sshd_config` or to `/assets/sshd_config` if you
-are using Omnibus Docker:
+Add the following to your `sshd_config` file. This is usuaully located at
+`/etc/ssh/sshd_config`, but it will be `/assets/sshd_config` if you're using
+Omnibus Docker:
```
-AuthorizedKeysCommand /opt/gitlab-shell/authorized_keys %u %k
+AuthorizedKeysCommand /opt/embedded/gitlab-shell/bin/gitlab-shell-authorized-keys-check git %u %k
AuthorizedKeysCommandUser git
```
@@ -70,7 +48,7 @@ Confirm that SSH is working by removing your user's SSH key in the UI, adding a
new one, and attempting to pull a repo.
> **Warning:** Do not disable writes until SSH is confirmed to be working
-perfectly because the file will quickly become out-of-date.
+perfectly, because the file will quickly become out-of-date.
In the case of lookup failures (which are not uncommon), the `authorized_keys`
file will still be scanned. So git SSH performance will still be slow for many
diff --git a/doc/administration/raketasks/check.md b/doc/administration/raketasks/check.md
index c39cb49b1c6..d1ed152b58c 100644
--- a/doc/administration/raketasks/check.md
+++ b/doc/administration/raketasks/check.md
@@ -76,6 +76,39 @@ Example output:
![gitlab:user:check_repos output](../img/raketasks/check_repos_output.png)
+## Uploaded Files Integrity
+
+The uploads check Rake task will loop through all uploads in the database
+and run two checks to determine the integrity of each file:
+
+1. Check if the file exist on the file system.
+1. Check if the checksum of the file on the file system matches the checksum in the database.
+
+**Omnibus Installation**
+
+```
+sudo gitlab-rake gitlab:uploads:check
+```
+
+**Source Installation**
+
+```bash
+sudo -u git -H bundle exec rake gitlab:uploads:check RAILS_ENV=production
+```
+
+This task also accepts some environment variables which you can use to override
+certain values:
+
+Variable | Type | Description
+-------- | ---- | -----------
+`BATCH` | integer | Specifies the size of the batch. Defaults to 200.
+`ID_FROM` | integer | Specifies the ID to start from, inclusive of the value.
+`ID_TO` | integer | Specifies the ID value to end at, inclusive of the value.
+
+```bash
+sudo gitlab-rake gitlab:uploads:check BATCH=100 ID_FROM=50 ID_TO=250
+```
+
## LDAP Check
The LDAP check Rake task will test the bind_dn and password credentials
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 24afcef9a31..22ccc6a46f3 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -468,6 +468,30 @@ Parameters:
}
```
+## List MR pipelines
+
+Get a list of merge request pipelines.
+
+```
+GET /projects/:id/merge_requests/:merge_request_iid/pipelines
+```
+
+Parameters:
+
+- `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
+[
+ {
+ "id": 77,
+ "sha": "959e04d7c7a30600c894bd3c0cd0e1ce7f42c11d",
+ "ref": "master",
+ "status": "success"
+ }
+]
+```
+
## Create MR
Creates a new merge request.
diff --git a/doc/api/snippets.md b/doc/api/snippets.md
index fdafbfb5b9e..e57143e4215 100644
--- a/doc/api/snippets.md
+++ b/doc/api/snippets.md
@@ -84,7 +84,11 @@ Parameters:
``` bash
-curl --request POST --data '{"title": "This is a snippet", "content": "Hello world", "description": "Hello World snippet", "file_name": "test.txt", "visibility": "internal" }' --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/snippets
+curl --request POST \
+ --data '{"title": "This is a snippet", "content": "Hello world", "description": "Hello World snippet", "file_name": "test.txt", "visibility": "internal" }' \
+ --header 'Content-Type: application/json' \
+ --header "PRIVATE-TOKEN: valid_api_token" \
+ https://gitlab.example.com/api/v4/snippets
```
Example response:
@@ -131,7 +135,11 @@ Parameters:
``` bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data '{"title": "foo", "content": "bar"}' https://gitlab.example.com/api/v4/snippets/1
+curl --request PUT \
+ --data '{"title": "foo", "content": "bar"}' \
+ --header 'Content-Type: application/json' \
+ --header "PRIVATE-TOKEN: valid_api_token" \
+ https://gitlab.example.com/api/v4/snippets/1
```
Example response:
diff --git a/doc/articles/how_to_install_git/index.md b/doc/articles/how_to_install_git/index.md
index 37b60501ce2..3e6003a33b7 100644
--- a/doc/articles/how_to_install_git/index.md
+++ b/doc/articles/how_to_install_git/index.md
@@ -1,66 +1 @@
-# Installing Git
-
-> **Article [Type](../../development/writing_documentation.html#types-of-technical-articles):** user guide ||
-> **Level:** beginner ||
-> **Author:** [Sean Packham](https://gitlab.com/SeanPackham) ||
-> **Publication date:** 2017-05-15
-
-To begin contributing to GitLab projects
-you will need to install the Git client on your computer.
-This article will show you how to install Git on macOS, Ubuntu Linux and Windows.
-
-## Install Git on macOS using the Homebrew package manager
-
-Although it is easy to use the version of Git shipped with macOS
-or install the latest version of Git on macOS by downloading it from the project website,
-we recommend installing it via Homebrew to get access to
-an extensive selection of dependancy managed libraries and applications.
-
-If you are sure you don't need access to any additional development libraries
-or don't have approximately 15gb of available disk space for Xcode and Homebrew
-use one of the the aforementioned methods.
-
-### Installing Xcode
-
-Xcode is needed by Homebrew to build dependencies.
-You can install [XCode](https://developer.apple.com/xcode/)
-through the macOS App Store.
-
-### Installing Homebrew
-
-Once Xcode is installed browse to the [Homebrew website](http://brew.sh/index.html)
-for the official Homebrew installation instructions.
-
-### Installing Git via Homebrew
-
-With Homebrew installed you are now ready to install Git.
-Open a Terminal and enter in the following command:
-
-```bash
-brew install git
-```
-
-Congratulations you should now have Git installed via Homebrew.
-Next read our article on [adding an SSH key to GitLab](../../ssh/README.md).
-
-## Install Git on Ubuntu Linux
-
-On Ubuntu and other Linux operating systems
-it is recommended to use the built in package manager to install Git.
-
-Open a Terminal and enter in the following commands
-to install the latest Git from the official Git maintained package archives:
-
-```bash
-sudo apt-add-repository ppa:git-core/ppa
-sudo apt-get update
-sudo apt-get install git
-```
-
-Congratulations you should now have Git installed via the Ubuntu package manager.
-Next read our article on [adding an SSH key to GitLab](../../ssh/README.md).
-
-## Installing Git on Windows from the Git website
-
-Browse to the [Git website](https://git-scm.com/) and download and install Git for Windows.
-Next read our article on [adding an SSH key to GitLab](../../ssh/README.md).
+This document was moved to [another location](../../topics/git/how_to_install_git/index.md).
diff --git a/doc/articles/index.md b/doc/articles/index.md
index 06675e15d76..d8a7874b96d 100644
--- a/doc/articles/index.md
+++ b/doc/articles/index.md
@@ -30,18 +30,6 @@ Build, test, and deploy the software you develop with [GitLab CI/CD](../ci/READM
| [How to use GitLab CI and MacStadium to build your macOS or iOS projects](https://about.gitlab.com/2017/05/15/how-to-use-macstadium-and-gitlab-ci-to-build-your-macos-or-ios-projects/) | Technical overview | 2017-05-15 |
| [Setting up GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/) | Tutorial | 2016-03-10 |
-## Git
-
-Learn how to use [Git with GitLab](../topics/git/index.md):
-
-| Article title | Category | Publishing date |
-| :------------ | :------: | --------------: |
-| [Numerous _undo_ possibilities in Git](numerous_undo_possibilities_in_git/index.md) | Tutorial | 2017-08-17 |
-| [Why Git is Worth the Learning Curve](https://about.gitlab.com/2017/05/17/learning-curve-is-the-biggest-challenge-developers-face-with-git/) | Concepts | 2017-05-17 |
-| [How to install Git](how_to_install_git/index.md) | Tutorial | 2017-05-15 |
-| [Getting Started with Git LFS](https://about.gitlab.com/2017/01/30/getting-started-with-git-lfs-tutorial/) | Tutorial | 2017-01-30 |
-| [Git Tips & Tricks](https://about.gitlab.com/2016/12/08/git-tips-and-tricks/) | Technical overview | 2016-12-08 |
-
## GitLab Pages
Learn how to deploy a static website with [GitLab Pages](../user/project/pages/index.md#getting-started):
diff --git a/doc/ci/examples/code_climate.md b/doc/ci/examples/code_climate.md
index 6a5821762cc..f919ed3c797 100644
--- a/doc/ci/examples/code_climate.md
+++ b/doc/ci/examples/code_climate.md
@@ -16,7 +16,8 @@ codequality:
- docker:dind
script:
- docker pull codeclimate/codeclimate
- - docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate analyze -f json > codeclimate.json || true
+ - docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate:0.69.0 init
+ - docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate:0.69.0 analyze -f json > codeclimate.json || true
artifacts:
paths: [codeclimate.json]
```
diff --git a/doc/development/fe_guide/style_guide_js.md b/doc/development/fe_guide/style_guide_js.md
index 1cd66f27492..02773162801 100644
--- a/doc/development/fe_guide/style_guide_js.md
+++ b/doc/development/fe_guide/style_guide_js.md
@@ -101,16 +101,16 @@ followed by any global declarations, then a blank newline prior to any imports o
```
Import statements are following usual naming guidelines, for example object literals use camel case:
-
+
```javascript
// some_object file
export default {
key: 'value',
};
-
+
// bad
import ObjectLiteral from 'some_object';
-
+
// good
import objectLiteral from 'some_object';
```
@@ -255,6 +255,10 @@ A forEach will cause side effects, it will be mutating the array being iterated.
### Vue.js
+#### `eslint-vue-plugin`
+We default to [eslint-vue-plugin][eslint-plugin-vue], with the `plugin:vue/recommended`.
+Please check this [rules][eslint-plugin-vue-rules] for more documentation.
+
#### Basic Rules
1. The service has it's own file
1. The store has it's own file
@@ -360,6 +364,10 @@ A forEach will cause side effects, it will be mutating the array being iterated.
<component
bar="bar"
/>
+
+ // bad
+ <component
+ bar="bar" />
```
#### Quotes
@@ -509,25 +517,7 @@ On those a default key should not be provided.
```
1. Properties in a Vue Component:
- 1. `name`
- 1. `props`
- 1. `mixins`
- 1. `directives`
- 1. `data`
- 1. `components`
- 1. `computedProps`
- 1. `methods`
- 1. `beforeCreate`
- 1. `created`
- 1. `beforeMount`
- 1. `mounted`
- 1. `beforeUpdate`
- 1. `updated`
- 1. `activated`
- 1. `deactivated`
- 1. `beforeDestroy`
- 1. `destroyed`
-
+ Check [order of properties in components rule][vue-order].
#### Vue and Bootstrap
@@ -582,3 +572,6 @@ The goal of this accord is to make sure we are all on the same page.
[eslintrc]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.eslintrc
[eslint-this]: http://eslint.org/docs/rules/class-methods-use-this
[eslint-new]: http://eslint.org/docs/rules/no-new
+[eslint-plugin-vue]: https://github.com/vuejs/eslint-plugin-vue
+[eslint-plugin-vue-rules]: https://github.com/vuejs/eslint-plugin-vue#bulb-rules
+[vue-order]: https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/order-in-components.md
diff --git a/doc/development/testing_guide/end_to_end_tests.md b/doc/development/testing_guide/end_to_end_tests.md
index abe5b06e0f0..5b4f6511f04 100644
--- a/doc/development/testing_guide/end_to_end_tests.md
+++ b/doc/development/testing_guide/end_to_end_tests.md
@@ -25,7 +25,7 @@ It is possible to run end-to-end tests (eventually being run within a
the `package-qa` manual action, that should be present in a merge request
widget.
-Mmanual action that starts end-to-end tests is also available in merge requests
+Manual action that starts end-to-end tests is also available in merge requests
in Omnibus GitLab project.
Below you can read more about how to use it and how does it work.
diff --git a/doc/topics/git/how_to_install_git/index.md b/doc/topics/git/how_to_install_git/index.md
new file mode 100644
index 00000000000..cdf61057449
--- /dev/null
+++ b/doc/topics/git/how_to_install_git/index.md
@@ -0,0 +1,66 @@
+# Installing Git
+
+> **[Article Type](../../../development/writing_documentation.html#types-of-technical-articles):** user guide ||
+> **Level:** beginner ||
+> **Author:** [Sean Packham](https://gitlab.com/SeanPackham) ||
+> **Publication date:** 2017-05-15
+
+To begin contributing to GitLab projects
+you will need to install the Git client on your computer.
+This article will show you how to install Git on macOS, Ubuntu Linux and Windows.
+
+## Install Git on macOS using the Homebrew package manager
+
+Although it is easy to use the version of Git shipped with macOS
+or install the latest version of Git on macOS by downloading it from the project website,
+we recommend installing it via Homebrew to get access to
+an extensive selection of dependancy managed libraries and applications.
+
+If you are sure you don't need access to any additional development libraries
+or don't have approximately 15gb of available disk space for Xcode and Homebrew
+use one of the the aforementioned methods.
+
+### Installing Xcode
+
+Xcode is needed by Homebrew to build dependencies.
+You can install [XCode](https://developer.apple.com/xcode/)
+through the macOS App Store.
+
+### Installing Homebrew
+
+Once Xcode is installed browse to the [Homebrew website](http://brew.sh/index.html)
+for the official Homebrew installation instructions.
+
+### Installing Git via Homebrew
+
+With Homebrew installed you are now ready to install Git.
+Open a Terminal and enter in the following command:
+
+```bash
+brew install git
+```
+
+Congratulations you should now have Git installed via Homebrew.
+Next read our article on [adding an SSH key to GitLab](../../../ssh/README.md).
+
+## Install Git on Ubuntu Linux
+
+On Ubuntu and other Linux operating systems
+it is recommended to use the built in package manager to install Git.
+
+Open a Terminal and enter in the following commands
+to install the latest Git from the official Git maintained package archives:
+
+```bash
+sudo apt-add-repository ppa:git-core/ppa
+sudo apt-get update
+sudo apt-get install git
+```
+
+Congratulations you should now have Git installed via the Ubuntu package manager.
+Next read our article on [adding an SSH key to GitLab](../../../ssh/README.md).
+
+## Installing Git on Windows from the Git website
+
+Browse to the [Git website](https://git-scm.com/) and download and install Git for Windows.
+Next read our article on [adding an SSH key to GitLab](../../../ssh/README.md).
diff --git a/doc/topics/git/index.md b/doc/topics/git/index.md
index 588f4fa369f..f69e2e49f0c 100644
--- a/doc/topics/git/index.md
+++ b/doc/topics/git/index.md
@@ -14,6 +14,7 @@ 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)
+- [How to install Git](how_to_install_git/index.md)
- [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)
@@ -21,27 +22,39 @@ We've gathered some resources to help you to get the best from Git with GitLab.
- [Revert a commit](../../user/project/merge_requests/revert_changes.md#reverting-a-commit)
- [Cherry-picking a commit](../../user/project/merge_requests/cherry_pick_changes.md#cherry-picking-a-commit)
- [Squashing commits](../../workflow/gitlab_flow.md#squashing-commits-with-rebase)
-- **Articles:**
- - [Numerous _undo_ possibilities in Git](../../articles/numerous_undo_possibilities_in_git/index.md)
- - [How to install Git](../../articles/how_to_install_git/index.md)
- - [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)
+
+**Third-party references:**
+
+- [Getting Started - Git website](https://git-scm.com)
+- [Getting Started - 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)
+
+### Concepts
+
+- Article (2017-05-17): [Why Git is Worth the Learning Curve](https://about.gitlab.com/2017/05/17/learning-curve-is-the-biggest-challenge-developers-face-with-git/)
+- Article (2016-05-11): [The future of SaaS hosted Git repository pricing](https://about.gitlab.com/2016/05/11/git-repository-pricing/)
+- GLU Course (Presentation): [About Version Control](https://docs.google.com/presentation/d/16sX7hUrCZyOFbpvnrAFrg6tVO5_yT98IgdAqOmXwBho/edit?usp=sharing)
+
+## Exploring Git
+
+- [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/)
+
+## Troubleshooting Git
+
+- [Numerous _undo_ possibilities in Git](../../articles/numerous_undo_possibilities_in_git/index.md)
+- Learn a few [Git troubleshooting](troubleshooting_git.md) techniques to help you out.
## 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)
+- [GitLab Flow](https://about.gitlab.com/2014/09/29/gitlab-flow/)
+
+**Third-party references:**
+
+- [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
@@ -55,17 +68,7 @@ We've gathered some resources to help you to get the best from Git with GitLab.
## Git LFS
-- [Git LFS](../../workflow/lfs/manage_large_binaries_with_git_lfs.md)
+- [Getting Started with Git LFS](https://about.gitlab.com/2017/01/30/getting-started-with-git-lfs-tutorial/)
+- [GitLab Git LFS documentation](../../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/)
-
-## Troubleshooting
-
-- Learn a few [Git troubleshooting](troubleshooting_git.md) techniques to help you out.
-
-## General information
-
-- **Articles:**
- - [The future of SaaS hosted Git repository pricing](https://about.gitlab.com/2016/05/11/git-repository-pricing/)
+- Article (2015-08-13): [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/)
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 4fa83388d0c..708d07fcec9 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -200,7 +200,7 @@ instance and project. In addition, all admins can use the admin interface under
|---------------------------------------|-----------------|-------------|----------|--------|
| See commits and jobs | ✓ | ✓ | ✓ | ✓ |
| Retry or cancel job | | ✓ | ✓ | ✓ |
-| Erase job artifacts and trace | | ✓ [^7] | ✓ | ✓ |
+| Erase job artifacts and trace | | ✓ [^5] | ✓ | ✓ |
| Remove project | | | ✓ | ✓ |
| Create project | | | ✓ | ✓ |
| Change project configuration | | | ✓ | ✓ |
@@ -223,13 +223,13 @@ users:
| Run CI job | | ✓ | ✓ | ✓ |
| Clone source and LFS from current project | | ✓ | ✓ | ✓ |
| Clone source and LFS from public projects | | ✓ | ✓ | ✓ |
-| Clone source and LFS from internal projects | | ✓ [^5] | ✓ [^5] | ✓ |
-| Clone source and LFS from private projects | | ✓ [^6] | ✓ [^6] | ✓ [^6] |
+| Clone source and LFS from internal projects | | ✓ [^6] | ✓ [^6] | ✓ |
+| Clone source and LFS from private projects | | ✓ [^7] | ✓ [^7] | ✓ [^7] |
| Push source and LFS | | | | |
| Pull container images from current project | | ✓ | ✓ | ✓ |
| Pull container images from public projects | | ✓ | ✓ | ✓ |
-| Pull container images from internal projects| | ✓ [^5] | ✓ [^5] | ✓ |
-| Pull container images from private projects | | ✓ [^6] | ✓ [^6] | ✓ [^6] |
+| Pull container images from internal projects| | ✓ [^6] | ✓ [^6] | ✓ |
+| Pull container images from private projects | | ✓ [^7] | ✓ [^7] | ✓ [^7] |
| Push container images to current project | | ✓ | ✓ | ✓ |
| Push container images to other projects | | | | |
@@ -259,12 +259,13 @@ with the permissions described on the documentation on [auditor users permission
Auditor users are available in [GitLab Enterprise Edition Premium](https://about.gitlab.com/gitlab-ee/)
only.
-[^1]: On public and internal projects, all users are able to perform this action.
+[^1]: On public and internal projects, all users are able to perform this action
[^2]: Guest users can only view the confidential issues they created themselves
[^3]: If **Public pipelines** is enabled in **Project Settings > CI/CD**
[^4]: Not allowed for Guest, Reporter, Developer, Master, or Owner
-[^5]: Only if user is not external one.
-[^6]: Only if user is a member of the project.
-[^7]: Only if the build was triggered by the user
+[^5]: Only if the job was triggered by the user
+[^6]: Only if user is not external one
+[^7]: Only if user is a member of the project
+
[ce-18994]: https://gitlab.com/gitlab-org/gitlab-ce/issues/18994
[new-mod]: project/new_ci_build_permissions_model.md
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index d5619c7b563..130f7897b1a 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -2,9 +2,6 @@
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/35954) in 10.1.
-CAUTION: **Warning:**
-The Cluster integration is currently in **Beta**.
-
With a cluster associated to your project, you can use Review Apps, deploy your
applications, run your pipelines, and much more, in an easy way.
@@ -25,11 +22,14 @@ prerequisites must be met:
be enabled in GitLab at the instance level. If that's not the case, ask your
administrator to enable it.
- Your associated Google account must have the right privileges to manage
- clusters on GKE. That would mean that a
- [billing account](https://cloud.google.com/billing/docs/how-to/manage-billing-account)
- must be set up.
-- You must have Master [permissions] in order to be able to access the **Cluster**
- page.
+ clusters on GKE. That would mean that a [billing
+ account](https://cloud.google.com/billing/docs/how-to/manage-billing-account)
+ must be set up and that you have to have permissions to access it.
+- You must have Master [permissions] in order to be able to access the
+ **Cluster** page.
+- You must have [Cloud Billing API](https://cloud.google.com/billing/) enabled
+- You must have [Resource Manager
+ API](https://cloud.google.com/resource-manager/)
If all of the above requirements are met, you can proceed to add a new GKE
cluster.
diff --git a/doc/user/project/integrations/redmine.md b/doc/user/project/integrations/redmine.md
index f530b6cb649..cc3218fbfd1 100644
--- a/doc/user/project/integrations/redmine.md
+++ b/doc/user/project/integrations/redmine.md
@@ -10,12 +10,7 @@ in the table below.
| `description` | A name for the issue tracker (to differentiate between instances, for example) |
| `project_url` | The URL to the project in Redmine which is being linked to this GitLab project |
| `issues_url` | The URL to the issue in Redmine project that is linked to this GitLab project. Note that the `issues_url` requires `:id` in the URL. This ID is used by GitLab as a placeholder to replace the issue number. |
- | `new_issue_url` | This is the URL to create a new issue in Redmine for the project linked to this GitLab project |
-
- Once you have configured and enabled Redmine:
- - the **Issues** link on the GitLab project pages takes you to the appropriate
- Redmine issue index
- - clicking **New issue** on the project dashboard creates a new Redmine issue
+ | `new_issue_url` | This is the URL to create a new issue in Redmine for the project linked to this GitLab project. **This is currently not being used and will be removed in a future release.** |
As an example, below is a configuration for a project named gitlab-ci.
diff --git a/features/support/env.rb b/features/support/env.rb
index 91a92314959..7f5b4c1c11b 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -10,14 +10,14 @@ if ENV['CI']
Knapsack::Adapters::SpinachAdapter.bind
end
-%w(select2_helper test_env repo_helpers wait_for_requests sidekiq project_forks_helper).each do |f|
+WebMock.enable!
+
+%w(select2_helper test_env repo_helpers wait_for_requests sidekiq project_forks_helper webmock).each do |f|
require Rails.root.join('spec', 'support', f)
end
Dir["#{Rails.root}/features/steps/shared/*.rb"].each { |file| require file }
-WebMock.allow_net_connect!
-
Spinach.hooks.before_run do
include RSpec::Mocks::ExampleMethods
RSpec::Mocks.setup
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 8f665b39fa8..420aaf1c964 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -24,6 +24,13 @@ module API
.preload(:notes, :author, :assignee, :milestone, :latest_merge_request_diff, :labels, :timelogs)
end
+ def merge_request_pipelines_with_access
+ authorize! :read_pipeline, user_project
+
+ mr = find_merge_request_with_access(params[:merge_request_iid])
+ mr.all_pipelines
+ end
+
params :merge_requests_params do
optional :state, type: String, values: %w[opened closed merged all], default: 'all',
desc: 'Return opened, closed, merged, or all merge requests'
@@ -214,6 +221,15 @@ module API
present merge_request, with: Entities::MergeRequestChanges, current_user: current_user
end
+ desc 'Get the merge request pipelines' do
+ success Entities::PipelineBasic
+ end
+ get ':id/merge_requests/:merge_request_iid/pipelines' do
+ pipelines = merge_request_pipelines_with_access
+
+ present paginate(pipelines), with: Entities::PipelineBasic
+ end
+
desc 'Update a merge request' do
success Entities::MergeRequest
end
diff --git a/lib/gitlab/background_migration/add_merge_request_diff_commits_count.rb b/lib/gitlab/background_migration/add_merge_request_diff_commits_count.rb
new file mode 100644
index 00000000000..7bffffec94d
--- /dev/null
+++ b/lib/gitlab/background_migration/add_merge_request_diff_commits_count.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+# rubocop:disable Style/Documentation
+# rubocop:disable Metrics/LineLength
+
+module Gitlab
+ module BackgroundMigration
+ class AddMergeRequestDiffCommitsCount
+ class MergeRequestDiff < ActiveRecord::Base
+ self.table_name = 'merge_request_diffs'
+ end
+
+ def perform(start_id, stop_id)
+ Rails.logger.info("Setting commits_count for merge request diffs: #{start_id} - #{stop_id}")
+
+ update = '
+ commits_count = (
+ SELECT count(*)
+ FROM merge_request_diff_commits
+ WHERE merge_request_diffs.id = merge_request_diff_commits.merge_request_diff_id
+ )'.squish
+
+ MergeRequestDiff.where(id: start_id..stop_id).update_all(update)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/bare_repository_import/importer.rb b/lib/gitlab/bare_repository_import/importer.rb
index 709a901aa77..884a3de8f62 100644
--- a/lib/gitlab/bare_repository_import/importer.rb
+++ b/lib/gitlab/bare_repository_import/importer.rb
@@ -63,6 +63,7 @@ module Gitlab
log " * Created #{project.name} (#{project_full_path})".color(:green)
project.write_repository_config
+ project.repository.create_hooks
ProjectCacheWorker.perform_async(project.id)
else
diff --git a/lib/gitlab/git/gitlab_projects.rb b/lib/gitlab/git/gitlab_projects.rb
index cba638c06db..976fa1ddfe6 100644
--- a/lib/gitlab/git/gitlab_projects.rb
+++ b/lib/gitlab/git/gitlab_projects.rb
@@ -41,36 +41,6 @@ module Gitlab
io.read
end
- def rm_project
- logger.info "Removing repository <#{repository_absolute_path}>."
- FileUtils.rm_rf(repository_absolute_path)
- end
-
- # Move repository from one directory to another
- #
- # Example: gitlab/gitlab-ci.git -> randx/six.git
- #
- # Won't work if target namespace directory does not exist
- #
- def mv_project(new_path)
- new_absolute_path = File.join(shard_path, new_path)
-
- # verify that the source repo exists
- unless File.exist?(repository_absolute_path)
- logger.error "mv-project failed: source path <#{repository_absolute_path}> does not exist."
- return false
- end
-
- # ...and that the target repo does not exist
- if File.exist?(new_absolute_path)
- logger.error "mv-project failed: destination path <#{new_absolute_path}> already exists."
- return false
- end
-
- logger.info "Moving repository from <#{repository_absolute_path}> to <#{new_absolute_path}>."
- FileUtils.mv(repository_absolute_path, new_absolute_path)
- end
-
# Import project via git clone --bare
# URL must be publicly cloneable
def import_project(source, timeout)
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 283134e043e..fa9bc57dd79 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -1103,14 +1103,27 @@ module Gitlab
end
end
- def write_ref(ref_path, ref)
+ def write_ref(ref_path, ref, old_ref: nil, shell: true)
+ if shell
+ shell_write_ref(ref_path, ref, old_ref)
+ else
+ rugged_write_ref(ref_path, ref)
+ end
+ end
+
+ def shell_write_ref(ref_path, ref, old_ref)
raise ArgumentError, "invalid ref_path #{ref_path.inspect}" if ref_path.include?(' ')
raise ArgumentError, "invalid ref #{ref.inspect}" if ref.include?("\x00")
+ raise ArgumentError, "invalid old_ref #{old_ref.inspect}" if !old_ref.nil? && old_ref.include?("\x00")
- input = "update #{ref_path}\x00#{ref}\x00\x00"
+ input = "update #{ref_path}\x00#{ref}\x00#{old_ref}\x00"
run_git!(%w[update-ref --stdin -z]) { |stdin| stdin.write(input) }
end
+ def rugged_write_ref(ref_path, ref)
+ rugged.references.create(ref_path, ref, force: true)
+ end
+
def fetch_ref(source_repository, source_ref:, target_ref:)
Gitlab::Git.check_namespace!(source_repository)
source_repository = RemoteRepository.new(source_repository) unless source_repository.is_a?(RemoteRepository)
diff --git a/lib/gitlab/gitaly_client/remote_service.rb b/lib/gitlab/gitaly_client/remote_service.rb
index 559a901b9a3..e58f641d69a 100644
--- a/lib/gitlab/gitaly_client/remote_service.rb
+++ b/lib/gitlab/gitaly_client/remote_service.rb
@@ -7,10 +7,12 @@ module Gitlab
@storage = repository.storage
end
- def add_remote(name, url, mirror_refmap)
+ def add_remote(name, url, mirror_refmaps)
request = Gitaly::AddRemoteRequest.new(
- repository: @gitaly_repo, name: name, url: url,
- mirror_refmap: mirror_refmap.to_s
+ repository: @gitaly_repo,
+ name: name,
+ url: url,
+ mirror_refmaps: Array.wrap(mirror_refmaps).map(&:to_s)
)
GitalyClient.call(@storage, :remote_service, :add_remote, request)
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index 392f66c99d3..f4a41dc3eda 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -136,7 +136,10 @@ module Gitlab
end
end
- # Move repository
+ # Move repository reroutes to mv_directory which is an alias for
+ # mv_namespace. Given the underlying implementation is a move action,
+ # indescriminate of what the folders might be.
+ #
# storage - project's storage path
# path - project disk path
# new_path - new project disk path
@@ -146,7 +149,9 @@ module Gitlab
#
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/873
def mv_repository(storage, path, new_path)
- gitlab_projects(storage, "#{path}.git").mv_project("#{new_path}.git")
+ return false if path.empty? || new_path.empty?
+
+ !!mv_directory(storage, "#{path}.git", "#{new_path}.git")
end
# Fork repository to new path
@@ -164,7 +169,9 @@ module Gitlab
.fork_repository(forked_to_storage, "#{forked_to_disk_path}.git")
end
- # Remove repository from file system
+ # Removes a repository from file system, using rm_diretory which is an alias
+ # for rm_namespace. Given the underlying implementation removes the name
+ # passed as second argument on the passed storage.
#
# storage - project's storage path
# name - project disk path
@@ -174,7 +181,12 @@ module Gitlab
#
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/873
def remove_repository(storage, name)
- gitlab_projects(storage, "#{name}.git").rm_project
+ return false if name.empty?
+
+ !!rm_directory(storage, "#{name}.git")
+ rescue ArgumentError => e
+ Rails.logger.warn("Repository does not exist: #{e} at: #{name}.git")
+ false
end
# Add new key to gitlab-shell
@@ -313,6 +325,7 @@ module Gitlab
rescue GRPC::InvalidArgument => e
raise ArgumentError, e.message
end
+ alias_method :rm_directory, :rm_namespace
# Move namespace directory inside repositories storage
#
@@ -332,6 +345,7 @@ module Gitlab
rescue GRPC::InvalidArgument
false
end
+ alias_method :mv_directory, :mv_namespace
def url_to_repo(path)
Gitlab.config.gitlab_shell.ssh_path_prefix + "#{path}.git"
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index 5ab6cd5a4ef..ce6d0422c1f 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -97,6 +97,9 @@ module Gitlab
)
end
+ # If present DisableCache must be a Boolean. Otherwise workhorse ignores it.
+ params['DisableCache'] = true if git_archive_cache_disabled?
+
[
SEND_DATA_HEADER,
"git-archive:#{encode(params)}"
@@ -244,6 +247,10 @@ module Gitlab
right_commit_id: diff_refs.head_sha
}
end
+
+ def git_archive_cache_disabled?
+ ENV['WORKHORSE_ARCHIVE_CACHE_DISABLED'].present? || Feature.enabled?(:workhorse_archive_cache_disabled)
+ end
end
end
end
diff --git a/lib/google_api/cloud_platform/client.rb b/lib/google_api/cloud_platform/client.rb
index f05d001fd02..ff638c07755 100644
--- a/lib/google_api/cloud_platform/client.rb
+++ b/lib/google_api/cloud_platform/client.rb
@@ -47,15 +47,15 @@ module GoogleApi
service.authorization = access_token
service.fetch_all(items: :projects) do |token|
- service.list_projects(page_token: token)
+ service.list_projects(page_token: token, options: user_agent_header)
end
end
- def projects_get_billing_info(project_name)
+ def projects_get_billing_info(project_id)
service = Google::Apis::CloudbillingV1::CloudbillingService.new
service.authorization = access_token
- service.get_project_billing_info("projects/#{project_name}")
+ service.get_project_billing_info("projects/#{project_id}", options: user_agent_header)
end
def projects_zones_clusters_get(project_id, zone, cluster_id)
diff --git a/lib/tasks/gitlab/uploads.rake b/lib/tasks/gitlab/uploads.rake
new file mode 100644
index 00000000000..df31567ce64
--- /dev/null
+++ b/lib/tasks/gitlab/uploads.rake
@@ -0,0 +1,44 @@
+namespace :gitlab do
+ namespace :uploads do
+ desc 'GitLab | Uploads | Check integrity of uploaded files'
+ task check: :environment do
+ puts 'Checking integrity of uploaded files'
+
+ uploads_batches do |batch|
+ batch.each do |upload|
+ puts "- Checking file (#{upload.id}): #{upload.absolute_path}".color(:green)
+
+ if upload.exist?
+ check_checksum(upload)
+ else
+ puts " * File does not exist on the file system".color(:red)
+ end
+ end
+ end
+
+ puts 'Done!'
+ end
+
+ def batch_size
+ ENV.fetch('BATCH', 200).to_i
+ end
+
+ def calculate_checksum(absolute_path)
+ Digest::SHA256.file(absolute_path).hexdigest
+ end
+
+ def check_checksum(upload)
+ checksum = calculate_checksum(upload.absolute_path)
+
+ if checksum != upload.checksum
+ puts " * File checksum (#{checksum}) does not match the one in the database (#{upload.checksum})".color(:red)
+ end
+ end
+
+ def uploads_batches(&block)
+ Upload.all.in_batches(of: batch_size, start: ENV['ID_FROM'], finish: ENV['ID_TO']) do |relation| # rubocop: disable Cop/InBatches
+ yield relation
+ end
+ end
+ end
+end
diff --git a/package.json b/package.json
index 5b9b90f0e77..4759ae76817 100644
--- a/package.json
+++ b/package.json
@@ -44,7 +44,6 @@
"document-register-element": "1.3.0",
"dropzone": "^4.2.0",
"emoji-unicode-version": "^0.2.1",
- "eslint-plugin-html": "^2.0.1",
"exports-loader": "^0.6.4",
"file-loader": "^0.11.1",
"fuzzaldrin-plus": "^0.5.0",
@@ -90,13 +89,15 @@
"@gitlab-org/gitlab-svgs": "^1.5.0",
"axios-mock-adapter": "^1.10.0",
"babel-plugin-istanbul": "^4.1.5",
- "eslint": "^3.10.1",
+ "eslint": "^3.18.0",
"eslint-config-airbnb-base": "^10.0.1",
"eslint-import-resolver-webpack": "^0.8.3",
"eslint-plugin-filenames": "^1.1.0",
+ "eslint-plugin-html": "2.0.1",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-jasmine": "^2.1.0",
"eslint-plugin-promise": "^3.5.0",
+ "eslint-plugin-vue": "^4.0.1",
"istanbul": "^0.4.5",
"jasmine-core": "^2.6.3",
"jasmine-jquery": "^2.1.1",
diff --git a/qa/README.md b/qa/README.md
index 7f2dd39ff63..8fa04e80825 100644
--- a/qa/README.md
+++ b/qa/README.md
@@ -27,13 +27,17 @@ following call would login to a local [GDK] instance and run all specs in
bin/qa Test::Instance http://localhost:3000
```
+### Writing tests
+
+1. [Using page objects](qa/page/README.md)
+
### Running specific tests
You can also supply specific tests to run as another parameter. For example, to
-test the EE license specs, you can run:
+run the repository-related specs, you can execute:
```
-EE_LICENSE="<YOUR LICENSE KEY>" bin/qa Test::Instance http://localhost qa/specs/features/ee
+bin/qa Test::Instance http://localhost qa/specs/features/repository/
```
Since the arguments would be passed to `rspec`, you could use all `rspec`
diff --git a/qa/qa.rb b/qa/qa.rb
index 71b80a6adcb..4803432aeee 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -58,6 +58,10 @@ module QA
module Integration
autoload :Mattermost, 'qa/scenario/test/integration/mattermost'
end
+
+ module Sanity
+ autoload :Selectors, 'qa/scenario/test/sanity/selectors'
+ end
end
end
@@ -68,6 +72,9 @@ module QA
#
module Page
autoload :Base, 'qa/page/base'
+ autoload :View, 'qa/page/view'
+ autoload :Element, 'qa/page/element'
+ autoload :Validator, 'qa/page/validator'
module Main
autoload :Login, 'qa/page/main/login'
diff --git a/qa/qa/factory/base.rb b/qa/qa/factory/base.rb
index 00851a7bece..bd66b74a164 100644
--- a/qa/qa/factory/base.rb
+++ b/qa/qa/factory/base.rb
@@ -1,12 +1,19 @@
+require 'forwardable'
+
module QA
module Factory
class Base
+ extend SingleForwardable
+
+ def_delegators :evaluator, :dependency, :dependencies
+ def_delegators :evaluator, :product, :attributes
+
def fabricate!(*_args)
raise NotImplementedError
end
def self.fabricate!(*args)
- Factory::Product.populate!(new) do |factory|
+ new.tap do |factory|
yield factory if block_given?
dependencies.each do |name, signature|
@@ -14,19 +21,37 @@ module QA
end
factory.fabricate!(*args)
+
+ return Factory::Product.populate!(self)
end
end
- def self.dependencies
- @dependencies ||= {}
+ def self.evaluator
+ @evaluator ||= Factory::Base::DSL.new(self)
end
- def self.dependency(factory, as:, &block)
- as.tap do |name|
- class_eval { attr_accessor name }
+ class DSL
+ attr_reader :dependencies, :attributes
+
+ def initialize(base)
+ @base = base
+ @dependencies = {}
+ @attributes = {}
+ end
+
+ def dependency(factory, as:, &block)
+ as.tap do |name|
+ @base.class_eval { attr_accessor name }
+
+ Dependency::Signature.new(factory, block).tap do |signature|
+ @dependencies.store(name, signature)
+ end
+ end
+ end
- Dependency::Signature.new(factory, block).tap do |signature|
- dependencies.store(name, signature)
+ def product(attribute, &block)
+ Product::Attribute.new(attribute, block).tap do |signature|
+ @attributes.store(attribute, signature)
end
end
end
diff --git a/qa/qa/factory/product.rb b/qa/qa/factory/product.rb
index df35bbbb443..d004e642f9b 100644
--- a/qa/qa/factory/product.rb
+++ b/qa/qa/factory/product.rb
@@ -5,8 +5,9 @@ module QA
class Product
include Capybara::DSL
- def initialize(factory)
- @factory = factory
+ Attribute = Struct.new(:name, :block)
+
+ def initialize
@location = current_url
end
@@ -15,11 +16,13 @@ module QA
end
def self.populate!(factory)
- raise ArgumentError unless block_given?
-
- yield factory
-
- new(factory)
+ new.tap do |product|
+ factory.attributes.each_value do |attribute|
+ product.instance_exec(&attribute.block).tap do |value|
+ product.define_singleton_method(attribute.name) { value }
+ end
+ end
+ end
end
end
end
diff --git a/qa/qa/factory/resource/project.rb b/qa/qa/factory/resource/project.rb
index 07c2e3086d1..7df2dc6618c 100644
--- a/qa/qa/factory/resource/project.rb
+++ b/qa/qa/factory/resource/project.rb
@@ -13,6 +13,10 @@ module QA
@description = 'My awesome project'
end
+ product :name do
+ Page::Project::Show.act { project_name }
+ end
+
def fabricate!
group.visit!
diff --git a/qa/qa/page/README.md b/qa/qa/page/README.md
new file mode 100644
index 00000000000..f72fbfeafca
--- /dev/null
+++ b/qa/qa/page/README.md
@@ -0,0 +1,112 @@
+# Page objects in GitLab QA
+
+In GitLab QA we are using a known pattern, called _Page Objects_.
+
+This means that we have built an abstraction for all GitLab pages that we use
+to drive GitLab QA scenarios. Whenever we do something on a page, like filling
+in a form, or clicking a button, we do that only through a page object
+associated with this area of GitLab.
+
+For example, when GitLab QA test harness signs in into GitLab, it needs to fill
+in a user login and user password. In order to do that, we have a class, called
+`Page::Main::Login` and `sign_in_using_credentials` methods, that is the only
+piece of the code, that has knowledge about `user_login` and `user_password`
+fields.
+
+## Why do we need that?
+
+We need page objects, because we need to reduce duplication and avoid problems
+whenever someone changes some selectors in GitLab's source code.
+
+Imagine that we have a hundred specs in GitLab QA, and we need to sign into
+GitLab each time, before we make assertions. Without a page object one would
+need to rely on volatile helpers or invoke Capybara methods directly. Imagine
+invoking `fill_in :user_login` in every `*_spec.rb` file / test example.
+
+When someone later changes `t.text_field :login` in the view associated with
+this page to `t.text_field :username` it will generate a different field
+identifier, what would effectively break all tests.
+
+Because we are using `Page::Main::Login.act { sign_in_using_credentials }`
+everywhere, when we want to sign into GitLab, the page object is the single
+source of truth, and we will need to update `fill_in :user_login`
+to `fill_in :user_username` only in a one place.
+
+## What problems did we have in the past?
+
+We do not run QA tests for every commit, because of performance reasons, and
+the time it would take to build packages and test everything.
+
+That is why when someone changes `t.text_field :login` to
+`t.text_field :username` in the _new session_ view we won't know about this
+change until our GitLab QA nightly pipeline fails, or until someone triggers
+`package-qa` action in their merge request.
+
+Obviously such a change would break all tests. We call this problem a _fragile
+tests problem_.
+
+In order to make GitLab QA more reliable and robust, we had to solve this
+problem by introducing coupling between GitLab CE / EE views and GitLab QA.
+
+## How did we solve fragile tests problem?
+
+Currently, when you add a new `Page::Base` derived class, you will also need to
+define all selectors that your page objects depends on.
+
+Whenever you push your code to CE / EE repository, `qa:selectors` sanity test
+job is going to be run as a part of a CI pipeline.
+
+This test is going to validate all page objects that we have implemented in
+`qa/page` directory. When it fails, you will be notified about missing
+or invalid views / selectors definition.
+
+## How to properly implement a page object?
+
+We have built a DSL to define coupling between a page object and GitLab views
+it is actually implemented by. See an example below.
+
+```ruby
+module Page
+ module Main
+ class Login < Page::Base
+ view 'app/views/devise/passwords/edit.html.haml' do
+ element :password_field, 'password_field :password'
+ element :password_confirmation, 'password_field :password_confirmation'
+ element :change_password_button, 'submit "Change your password"'
+ end
+
+ view 'app/views/devise/sessions/_new_base.html.haml' do
+ element :login_field, 'text_field :login'
+ element :passowrd_field, 'password_field :password'
+ element :sign_in_button, 'submit "Sign in"'
+ end
+
+ # ...
+ end
+end
+```
+
+It is possible to use `element` DSL method without value, with a String value
+or with a Regexp.
+
+```ruby
+view 'app/views/my/view.html.haml' do
+ # Require `f.submit "Sign in"` to be present in `my/view.html.haml
+ element :my_button, 'f.submit "Sign in"'
+
+ # Match every line in `my/view.html.haml` against
+ # `/link_to .* "My Profile"/` regexp.
+ element :profile_link, /link_to .* "My Profile"/
+
+ # Implicitly require `.qa-logout-button` CSS class to be present in the view
+ element :logout_button
+end
+```
+
+## Where to ask for help?
+
+If you need more information, ask for help on `#qa` channel on Slack (GitLab
+Team only).
+
+If you are not a Team Member, and you still need help to contribute, please
+open an issue in GitLab QA issue tracker.
diff --git a/qa/qa/page/admin/settings.rb b/qa/qa/page/admin/settings.rb
index 39e2f2062ad..1904732aee6 100644
--- a/qa/qa/page/admin/settings.rb
+++ b/qa/qa/page/admin/settings.rb
@@ -2,6 +2,13 @@ module QA
module Page
module Admin
class Settings < Page::Base
+ ##
+ # TODO, define all selectors required by this page object
+ #
+ # See gitlab-org/gitlab-qa#154
+ #
+ view 'app/views/admin/application_settings/show.html.haml'
+
def enable_hashed_storage
scroll_to 'legend', text: 'Repository Storage'
check 'Create new projects using hashed storage paths'
diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb
index 99eba02b6e3..ea4c920c82c 100644
--- a/qa/qa/page/base.rb
+++ b/qa/qa/page/base.rb
@@ -5,6 +5,9 @@ module QA
class Base
include Capybara::DSL
include Scenario::Actable
+ extend SingleForwardable
+
+ def_delegators :evaluator, :view, :views
def refresh
visit current_url
@@ -37,9 +40,39 @@ module QA
page.within(selector) { yield } if block_given?
end
+ def click_element(name)
+ find(Page::Element.new(name).selector_css).click
+ end
+
def self.path
raise NotImplementedError
end
+
+ def self.evaluator
+ @evaluator ||= Page::Base::DSL.new
+ end
+
+ def self.errors
+ if views.empty?
+ return ["Page class does not have views / elements defined!"]
+ end
+
+ views.map(&:errors).flatten
+ end
+
+ class DSL
+ attr_reader :views
+
+ def initialize
+ @views = []
+ end
+
+ def view(path, &block)
+ Page::View.evaluate(&block).tap do |view|
+ @views.push(Page::View.new(path, view.elements))
+ end
+ end
+ end
end
end
end
diff --git a/qa/qa/page/dashboard/groups.rb b/qa/qa/page/dashboard/groups.rb
index 083d2e1ab16..e853e0d85e0 100644
--- a/qa/qa/page/dashboard/groups.rb
+++ b/qa/qa/page/dashboard/groups.rb
@@ -2,6 +2,15 @@ module QA
module Page
module Dashboard
class Groups < Page::Base
+ view 'app/views/shared/groups/_search_form.html.haml' do
+ element :groups_filter, 'search_field_tag :filter'
+ element :groups_filter_placeholder, 'Filter by name...'
+ end
+
+ view 'app/views/dashboard/_groups_head.html.haml' do
+ element :new_group_button, 'link_to _("New group")'
+ end
+
def filter_by_name(name)
fill_in 'Filter by name...', with: name
end
diff --git a/qa/qa/page/dashboard/projects.rb b/qa/qa/page/dashboard/projects.rb
index 7ed27da6d89..71255b18362 100644
--- a/qa/qa/page/dashboard/projects.rb
+++ b/qa/qa/page/dashboard/projects.rb
@@ -2,6 +2,8 @@ module QA
module Page
module Dashboard
class Projects < Page::Base
+ view 'app/views/dashboard/projects/index.html.haml'
+
def go_to_project(name)
find_link(text: name).click
end
diff --git a/qa/qa/page/element.rb b/qa/qa/page/element.rb
new file mode 100644
index 00000000000..9944a39ce07
--- /dev/null
+++ b/qa/qa/page/element.rb
@@ -0,0 +1,32 @@
+module QA
+ module Page
+ class Element
+ attr_reader :name
+
+ def initialize(name, pattern = nil)
+ @name = name
+ @pattern = pattern || selector
+ end
+
+ def selector
+ "qa-#{@name.to_s.tr('_', '-')}"
+ end
+
+ def selector_css
+ ".#{selector}"
+ end
+
+ def expression
+ if @pattern.is_a?(String)
+ @_regexp ||= Regexp.new(Regexp.escape(@pattern))
+ else
+ @pattern
+ end
+ end
+
+ def matches?(line)
+ !!(line =~ expression)
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/group/new.rb b/qa/qa/page/group/new.rb
index 53fdaaed078..48b71a7c883 100644
--- a/qa/qa/page/group/new.rb
+++ b/qa/qa/page/group/new.rb
@@ -2,6 +2,17 @@ module QA
module Page
module Group
class New < Page::Base
+ view 'app/views/shared/_group_form.html.haml' do
+ element :group_path_field, 'text_field :path'
+ element :group_name_field, 'text_field :name'
+ element :group_description_field, 'text_area :description'
+ end
+
+ view 'app/views/groups/new.html.haml' do
+ element :create_group_button, "submit 'Create group'"
+ element :visibility_radios, 'visibility_level:'
+ end
+
def set_path(path)
fill_in 'group_path', with: path
fill_in 'group_name', with: path
diff --git a/qa/qa/page/group/show.rb b/qa/qa/page/group/show.rb
index 0a16c07d64b..37ed3b35bce 100644
--- a/qa/qa/page/group/show.rb
+++ b/qa/qa/page/group/show.rb
@@ -2,6 +2,13 @@ module QA
module Page
module Group
class Show < Page::Base
+ ##
+ # TODO, define all selectors required by this page object
+ #
+ # See gitlab-org/gitlab-qa#154
+ #
+ view 'app/views/groups/show.html.haml'
+
def go_to_subgroup(name)
click_link name
end
diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb
index f88325f408b..7b4c1603017 100644
--- a/qa/qa/page/main/login.rb
+++ b/qa/qa/page/main/login.rb
@@ -2,6 +2,18 @@ module QA
module Page
module Main
class Login < Page::Base
+ view 'app/views/devise/passwords/edit.html.haml' do
+ element :password_field, 'password_field :password'
+ element :password_confirmation, 'password_field :password_confirmation'
+ element :change_password_button, 'submit "Change your password"'
+ end
+
+ view 'app/views/devise/sessions/_new_base.html.haml' do
+ element :login_field, 'text_field :login'
+ element :passowrd_field, 'password_field :password'
+ element :sign_in_button, 'submit "Sign in"'
+ end
+
def initialize
wait('.application', time: 500)
end
diff --git a/qa/qa/page/main/oauth.rb b/qa/qa/page/main/oauth.rb
index e746cff0a80..6f548148363 100644
--- a/qa/qa/page/main/oauth.rb
+++ b/qa/qa/page/main/oauth.rb
@@ -2,6 +2,10 @@ module QA
module Page
module Main
class OAuth < Page::Base
+ view 'app/views/doorkeeper/authorizations/new.html.haml' do
+ element :authorization_button, 'submit_tag "Authorize"'
+ end
+
def needs_authorization?
page.current_url.include?('/oauth')
end
diff --git a/qa/qa/page/mattermost/login.rb b/qa/qa/page/mattermost/login.rb
index 8ffd4fdad13..9b21300ea3c 100644
--- a/qa/qa/page/mattermost/login.rb
+++ b/qa/qa/page/mattermost/login.rb
@@ -2,6 +2,13 @@ module QA
module Page
module Mattermost
class Login < Page::Base
+ ##
+ # TODO, define all selectors required by this page object
+ #
+ # See gitlab-org/gitlab-qa#154
+ #
+ view 'app/views/projects/mattermosts/new.html.haml'
+
def sign_in_using_oauth
click_link class: 'btn btn-custom-login gitlab'
diff --git a/qa/qa/page/mattermost/main.rb b/qa/qa/page/mattermost/main.rb
index 4b8fc28e53f..bc2f9acc729 100644
--- a/qa/qa/page/mattermost/main.rb
+++ b/qa/qa/page/mattermost/main.rb
@@ -2,6 +2,13 @@ module QA
module Page
module Mattermost
class Main < Page::Base
+ ##
+ # TODO, define all selectors required by this page object
+ #
+ # See gitlab-org/gitlab-qa#154
+ #
+ view 'app/views/projects/mattermosts/new.html.haml'
+
def initialize
visit(Runtime::Scenario.mattermost_address)
end
diff --git a/qa/qa/page/menu/admin.rb b/qa/qa/page/menu/admin.rb
index 07fe40fda3a..40da4a53e8a 100644
--- a/qa/qa/page/menu/admin.rb
+++ b/qa/qa/page/menu/admin.rb
@@ -2,6 +2,13 @@ module QA
module Page
module Menu
class Admin < Page::Base
+ ##
+ # TODO, define all selectors required by this page object
+ #
+ # See gitlab-org/gitlab-qa#154
+ #
+ view 'app/views/admin/dashboard/index.html.haml'
+
def go_to_license
click_link 'License'
end
diff --git a/qa/qa/page/menu/main.rb b/qa/qa/page/menu/main.rb
index b94c2c6c23d..f8978b8a5f7 100644
--- a/qa/qa/page/menu/main.rb
+++ b/qa/qa/page/menu/main.rb
@@ -2,19 +2,40 @@ module QA
module Page
module Menu
class Main < Page::Base
+ view 'app/views/layouts/header/_default.html.haml' do
+ element :navbar
+ element :user_avatar
+ element :user_menu, '.dropdown-menu-nav'
+ element :user_sign_out_link, 'link_to "Sign out"'
+ end
+
+ view 'app/views/layouts/nav/_dashboard.html.haml' do
+ element :admin_area_link
+ element :projects_dropdown
+ element :groups_link
+ end
+
+ view 'app/views/layouts/nav/projects_dropdown/_show.html.haml' do
+ element :projects_dropdown_sidebar
+ element :your_projects_link
+ end
+
def go_to_groups
- within_top_menu { click_link 'Groups' }
+ within_top_menu { click_element :groups_link }
end
def go_to_projects
within_top_menu do
- click_link 'Projects'
- click_link 'Your projects'
+ click_element :projects_dropdown
+ end
+
+ page.within('.qa-projects-dropdown-sidebar') do
+ click_element :your_projects_link
end
end
def go_to_admin_area
- within_top_menu { find('.admin-icon').click }
+ within_top_menu { click_element :admin_area_link }
end
def sign_out
@@ -24,20 +45,20 @@ module QA
end
def has_personal_area?
- page.has_selector?('.header-user-dropdown-toggle')
+ page.has_selector?('.qa-user-avatar')
end
private
def within_top_menu
- page.within('.navbar') do
+ page.within('.qa-navbar') do
yield
end
end
def within_user_menu
within_top_menu do
- find('.header-user-dropdown-toggle').click
+ click_element :user_avatar
page.within('.dropdown-menu-nav') do
yield
diff --git a/qa/qa/page/menu/side.rb b/qa/qa/page/menu/side.rb
index 6c25aba4bac..1df4e0c2429 100644
--- a/qa/qa/page/menu/side.rb
+++ b/qa/qa/page/menu/side.rb
@@ -2,6 +2,12 @@ module QA
module Page
module Menu
class Side < Page::Base
+ view 'app/views/layouts/nav/sidebar/_project.html.haml' do
+ element :settings_item
+ element :repository_link, "title: 'Repository'"
+ element :top_level_items, '.sidebar-top-level-items'
+ end
+
def click_repository_setting
hover_setting do
click_link('Repository')
@@ -12,7 +18,7 @@ module QA
def hover_setting
within_sidebar do
- find('.nav-item-name', text: 'Settings').hover
+ find('.qa-settings-item').hover
yield
end
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
index b31bec27b59..9b1438f76d5 100644
--- a/qa/qa/page/project/new.rb
+++ b/qa/qa/page/project/new.rb
@@ -2,9 +2,18 @@ module QA
module Page
module Project
class New < Page::Base
+ view 'app/views/projects/_new_project_fields.html.haml' do
+ element :project_namespace_select
+ element :project_namespace_field, 'select :namespace_id'
+ element :project_path, 'text_field :path'
+ element :project_description, 'text_area :description'
+ element :project_create_button, "submit 'Create project'"
+ end
+
def choose_test_namespace
- find('#s2id_project_namespace_id').click
- find('.select2-result-label', text: Runtime::Namespace.name).click
+ click_element :project_namespace_select
+
+ first('li', text: Runtime::Namespace.path).click
end
def choose_name(name)
diff --git a/qa/qa/page/project/settings/deploy_keys.rb b/qa/qa/page/project/settings/deploy_keys.rb
index 4028b8cccc5..a8d6f09777c 100644
--- a/qa/qa/page/project/settings/deploy_keys.rb
+++ b/qa/qa/page/project/settings/deploy_keys.rb
@@ -3,6 +3,13 @@ module QA
module Project
module Settings
class DeployKeys < Page::Base
+ ##
+ # TODO, define all selectors required by this page object
+ #
+ # See gitlab-org/gitlab-qa#154
+ #
+ view 'app/views/projects/deploy_keys/edit.html.haml'
+
def fill_key_title(title)
fill_in 'deploy_key_title', with: title
end
diff --git a/qa/qa/page/project/settings/repository.rb b/qa/qa/page/project/settings/repository.rb
index 034b0d09c1c..524d87c6be9 100644
--- a/qa/qa/page/project/settings/repository.rb
+++ b/qa/qa/page/project/settings/repository.rb
@@ -5,6 +5,13 @@ module QA
class Repository < Page::Base
include Common
+ ##
+ # TODO, define all selectors required by this page object
+ #
+ # See gitlab-org/gitlab-qa#154
+ #
+ view 'app/views/projects/settings/repository/show.html.haml'
+
def expand_deploy_keys(&block)
expand('.qa-expand-deploy-keys') do
DeployKeys.perform(&block)
diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb
index 3b2bac84f3f..c8af5ba6280 100644
--- a/qa/qa/page/project/show.rb
+++ b/qa/qa/page/project/show.rb
@@ -2,8 +2,21 @@ module QA
module Page
module Project
class Show < Page::Base
+ view 'app/views/shared/_clone_panel.html.haml' do
+ element :clone_dropdown
+ element :clone_options_dropdown, '.clone-options-dropdown'
+ end
+
+ view 'app/views/shared/_clone_panel.html.haml' do
+ element :project_repository_location, 'text_field_tag :project_clone'
+ end
+
+ view 'app/views/projects/_home_panel.html.haml' do
+ element :project_name
+ end
+
def choose_repository_clone_http
- find('#clone-dropdown').click
+ click_element :clone_dropdown
page.within('.clone-options-dropdown') do
click_link('HTTP')
@@ -15,7 +28,7 @@ module QA
end
def project_name
- find('.project-title').text
+ find('.qa-project-name').text
end
def wait_for_push
diff --git a/qa/qa/page/validator.rb b/qa/qa/page/validator.rb
new file mode 100644
index 00000000000..117d8d4db67
--- /dev/null
+++ b/qa/qa/page/validator.rb
@@ -0,0 +1,52 @@
+module QA
+ module Page
+ class Validator
+ ValidationError = Class.new(StandardError)
+
+ Error = Struct.new(:page, :message) do
+ def to_s
+ "Error: #{page} - #{message}"
+ end
+ end
+
+ def initialize(constant)
+ @module = constant
+ end
+
+ def constants
+ @consts ||= @module.constants.map do |const|
+ @module.const_get(const)
+ end
+ end
+
+ def descendants
+ @descendants ||= constants.map do |const|
+ case const
+ when Class
+ const if const < Page::Base
+ when Module
+ Page::Validator.new(const).descendants
+ end
+ end
+
+ @descendants.flatten.compact
+ end
+
+ def errors
+ [].tap do |errors|
+ descendants.each do |page|
+ page.errors.each do |message|
+ errors.push(Error.new(page.name, message))
+ end
+ end
+ end
+ end
+
+ def validate!
+ return if errors.none?
+
+ raise ValidationError, 'Page views / elements validation error!'
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/view.rb b/qa/qa/page/view.rb
new file mode 100644
index 00000000000..6635e1ce039
--- /dev/null
+++ b/qa/qa/page/view.rb
@@ -0,0 +1,55 @@
+module QA
+ module Page
+ class View
+ attr_reader :path, :elements
+
+ def initialize(path, elements)
+ @path = path
+ @elements = elements
+ end
+
+ def pathname
+ @pathname ||= Pathname.new(File.join(__dir__, '../../../', @path))
+ .cleanpath.expand_path
+ end
+
+ def errors
+ unless pathname.readable?
+ return ["Missing view partial `#{pathname}`!"]
+ end
+
+ ##
+ # Reduce required elements by streaming view and making assertions on
+ # elements' existence.
+ #
+ @missing ||= @elements.dup.tap do |elements|
+ File.foreach(pathname.to_s) do |line|
+ elements.reject! { |element| element.matches?(line) }
+ end
+ end
+
+ @missing.map do |missing|
+ "Missing element `#{missing.name}` in `#{pathname}` view partial!"
+ end
+ end
+
+ def self.evaluate(&block)
+ Page::View::DSL.new.tap do |evaluator|
+ evaluator.instance_exec(&block) if block_given?
+ end
+ end
+
+ class DSL
+ attr_reader :elements
+
+ def initialize
+ @elements = []
+ end
+
+ def element(name, pattern = nil)
+ @elements.push(Page::Element.new(name, pattern))
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/runtime/namespace.rb b/qa/qa/runtime/namespace.rb
index b00e925986b..a72c2d21898 100644
--- a/qa/qa/runtime/namespace.rb
+++ b/qa/qa/runtime/namespace.rb
@@ -11,6 +11,10 @@ module QA
'qa-test-' + time.strftime('%d-%m-%Y-%H-%M-%S')
end
+ def path
+ "#{sandbox_name}/#{name}"
+ end
+
def sandbox_name
'gitlab-qa-sandbox'
end
diff --git a/qa/qa/scenario/test/sanity/selectors.rb b/qa/qa/scenario/test/sanity/selectors.rb
new file mode 100644
index 00000000000..c87eb5f3dfb
--- /dev/null
+++ b/qa/qa/scenario/test/sanity/selectors.rb
@@ -0,0 +1,54 @@
+module QA
+ module Scenario
+ module Test
+ module Sanity
+ class Selectors < Scenario::Template
+ include Scenario::Bootable
+
+ PAGES = [QA::Page].freeze
+
+ def perform(*)
+ validators = PAGES.map do |pages|
+ Page::Validator.new(pages)
+ end
+
+ validators.map(&:errors).flatten.tap do |errors|
+ break if errors.none?
+
+ warn <<~EOS
+ GitLab QA sanity selectors validation test detected problems
+ with your merge request!
+
+ The purpose of this test is to make sure that GitLab QA tests,
+ that are entirely black-box, click-driven scenarios, do match
+ pages structure / layout in GitLab CE / EE repositories.
+
+ It looks like you have changed views / pages / selectors, and
+ these are now out of sync with what we have defined in `qa/`
+ directory.
+
+ Please update the code in `qa/` directory to make it match
+ current changes in this merge request.
+
+ For more help see documentation in `qa/page/README.md` file or
+ ask for help on #qa channel on Slack (GitLab Team only).
+
+ If you are not a Team Member, and you still need help to
+ contribute, please open an issue in GitLab QA issue tracker.
+
+ Please see errors described below.
+
+ EOS
+
+ warn errors
+ end
+
+ validators.each(&:validate!)
+
+ puts 'Views / selectors validation passed!'
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/project/create_spec.rb b/qa/qa/specs/features/project/create_spec.rb
index 61c19378ae0..b1c07249892 100644
--- a/qa/qa/specs/features/project/create_spec.rb
+++ b/qa/qa/specs/features/project/create_spec.rb
@@ -4,11 +4,13 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
- Factory::Resource::Project.fabricate! do |project|
+ created_project = Factory::Resource::Project.fabricate! do |project|
project.name = 'awesome-project'
project.description = 'create awesome project test'
end
+ expect(created_project.name).to match /^awesome-project-\h{16}$/
+
expect(page).to have_content(
/Project \S?awesome-project\S+ was successfully created/
)
diff --git a/qa/spec/factory/base_spec.rb b/qa/spec/factory/base_spec.rb
index a3ba0176819..90dd58e20fd 100644
--- a/qa/spec/factory/base_spec.rb
+++ b/qa/spec/factory/base_spec.rb
@@ -1,8 +1,9 @@
describe QA::Factory::Base do
+ let(:factory) { spy('factory') }
+ let(:product) { spy('product') }
+
describe '.fabricate!' do
subject { Class.new(described_class) }
- let(:factory) { spy('factory') }
- let(:product) { spy('product') }
before do
allow(QA::Factory::Product).to receive(:new).and_return(product)
@@ -59,30 +60,63 @@ describe QA::Factory::Base do
it 'defines dependency accessors' do
expect(subject.new).to respond_to :mydep, :mydep=
end
- end
- describe 'building dependencies' do
- let(:dependency) { double('dependency') }
- let(:instance) { spy('instance') }
+ describe 'dependencies fabrication' do
+ let(:dependency) { double('dependency') }
+ let(:instance) { spy('instance') }
+
+ subject do
+ Class.new(described_class) do
+ dependency Some::MyDependency, as: :mydep
+ end
+ end
+
+ before do
+ stub_const('Some::MyDependency', dependency)
+ allow(subject).to receive(:new).and_return(instance)
+ allow(instance).to receive(:mydep).and_return(nil)
+ allow(QA::Factory::Product).to receive(:new)
+ end
+
+ it 'builds all dependencies first' do
+ expect(dependency).to receive(:fabricate!).once
+
+ subject.fabricate!
+ end
+ end
+ end
+
+ describe '.product' do
subject do
Class.new(described_class) do
- dependency Some::MyDependency, as: :mydep
+ product :token do
+ page.do_something_on_page!
+ 'resulting value'
+ end
end
end
- before do
- stub_const('Some::MyDependency', dependency)
-
- allow(subject).to receive(:new).and_return(instance)
- allow(instance).to receive(:mydep).and_return(nil)
- allow(QA::Factory::Product).to receive(:new)
+ it 'appends new product attribute' do
+ expect(subject.attributes).to be_one
+ expect(subject.attributes).to have_key(:token)
end
- it 'builds all dependencies first' do
- expect(dependency).to receive(:fabricate!).once
+ describe 'populating fabrication product with data' do
+ let(:page) { spy('page') }
+
+ before do
+ allow(subject).to receive(:new).and_return(factory)
+ allow(QA::Factory::Product).to receive(:new).and_return(product)
+ allow(product).to receive(:page).and_return(page)
+ end
- subject.fabricate!
+ it 'populates product after fabrication' do
+ subject.fabricate!
+
+ expect(page).to have_received(:do_something_on_page!)
+ expect(product.token).to eq 'resulting value'
+ end
end
end
end
diff --git a/qa/spec/factory/product_spec.rb b/qa/spec/factory/product_spec.rb
index 3d9e86a641b..fdfb1ec90cc 100644
--- a/qa/spec/factory/product_spec.rb
+++ b/qa/spec/factory/product_spec.rb
@@ -3,19 +3,8 @@ describe QA::Factory::Product do
let(:product) { spy('product') }
describe '.populate!' do
- it 'instantiates and yields factory' do
- expect(described_class).to receive(:new).with(factory)
-
- described_class.populate!(factory) do |instance|
- instance.something = 'string'
- end
-
- expect(factory).to have_received(:something=).with('string')
- end
-
it 'returns a fabrication product' do
- expect(described_class).to receive(:new)
- .with(factory).and_return(product)
+ expect(described_class).to receive(:new).and_return(product)
result = described_class.populate!(factory) do |instance|
instance.something = 'string'
@@ -23,11 +12,6 @@ describe QA::Factory::Product do
expect(result).to be product
end
-
- it 'raises unless block given' do
- expect { described_class.populate!(factory) }
- .to raise_error ArgumentError
- end
end
describe '.visit!' do
@@ -37,8 +21,7 @@ describe QA::Factory::Product do
allow_any_instance_of(described_class)
.to receive(:visit).and_return('visited some url')
- expect(described_class.new(factory).visit!)
- .to eq 'visited some url'
+ expect(subject.visit!).to eq 'visited some url'
end
end
end
diff --git a/qa/spec/page/base_spec.rb b/qa/spec/page/base_spec.rb
new file mode 100644
index 00000000000..287adf35c46
--- /dev/null
+++ b/qa/spec/page/base_spec.rb
@@ -0,0 +1,63 @@
+describe QA::Page::Base do
+ describe 'page helpers' do
+ it 'exposes helpful page helpers' do
+ expect(subject).to respond_to :refresh, :wait, :scroll_to
+ end
+ end
+
+ describe '.view', 'DSL for defining view partials' do
+ subject do
+ Class.new(described_class) do
+ view 'path/to/some/view.html.haml' do
+ element :something, 'string pattern'
+ element :something_else, /regexp pattern/
+ end
+
+ view 'path/to/some/_partial.html.haml' do
+ element :something, 'string pattern'
+ end
+ end
+ end
+
+ it 'makes it possible to define page views' do
+ expect(subject.views.size).to eq 2
+ expect(subject.views).to all(be_an_instance_of QA::Page::View)
+ end
+
+ it 'populates views objects with data about elements' do
+ subject.views.first.elements.tap do |elements|
+ expect(elements.size).to eq 2
+ expect(elements).to all(be_an_instance_of QA::Page::Element)
+ expect(elements.map(&:name)).to eq [:something, :something_else]
+ end
+ end
+ end
+
+ describe '.errors' do
+ let(:view) { double('view') }
+
+ context 'when page has views and elements defined' do
+ before do
+ allow(described_class).to receive(:views)
+ .and_return([view])
+
+ allow(view).to receive(:errors).and_return(['some error'])
+ end
+
+ it 'iterates views composite and returns errors' do
+ expect(described_class.errors).to eq ['some error']
+ end
+ end
+
+ context 'when page has no views and elements defined' do
+ before do
+ allow(described_class).to receive(:views).and_return([])
+ end
+
+ it 'appends an error about missing views / elements block' do
+ expect(described_class.errors)
+ .to include 'Page class does not have views / elements defined!'
+ end
+ end
+ end
+end
diff --git a/qa/spec/page/element_spec.rb b/qa/spec/page/element_spec.rb
new file mode 100644
index 00000000000..8598c57ad34
--- /dev/null
+++ b/qa/spec/page/element_spec.rb
@@ -0,0 +1,51 @@
+describe QA::Page::Element do
+ describe '#selector' do
+ it 'transforms element name into QA-specific selector' do
+ expect(described_class.new(:sign_in_button).selector)
+ .to eq 'qa-sign-in-button'
+ end
+ end
+
+ describe '#selector_css' do
+ it 'transforms element name into QA-specific clickable css selector' do
+ expect(described_class.new(:sign_in_button).selector_css)
+ .to eq '.qa-sign-in-button'
+ end
+ end
+
+ context 'when pattern is an expression' do
+ subject { described_class.new(:something, /button 'Sign in'/) }
+
+ it 'matches when there is a match' do
+ expect(subject.matches?("button 'Sign in'")).to be true
+ end
+
+ it 'does not match if pattern is not present' do
+ expect(subject.matches?("button 'Sign out'")).to be false
+ end
+ end
+
+ context 'when pattern is a string' do
+ subject { described_class.new(:something, 'button') }
+
+ it 'matches when there is match' do
+ expect(subject.matches?('some button in the view')).to be true
+ end
+
+ it 'does not match if pattern is not present' do
+ expect(subject.matches?('text_field :name')).to be false
+ end
+ end
+
+ context 'when pattern is not provided' do
+ subject { described_class.new(:some_name) }
+
+ it 'matches when QA specific selector is present' do
+ expect(subject.matches?('some qa-some-name selector')).to be true
+ end
+
+ it 'does not match if QA selector is not there' do
+ expect(subject.matches?('some_name selector')).to be false
+ end
+ end
+end
diff --git a/qa/spec/page/validator_spec.rb b/qa/spec/page/validator_spec.rb
new file mode 100644
index 00000000000..02822d7d18f
--- /dev/null
+++ b/qa/spec/page/validator_spec.rb
@@ -0,0 +1,79 @@
+describe QA::Page::Validator do
+ describe '#constants' do
+ subject do
+ described_class.new(QA::Page::Project)
+ end
+
+ it 'returns all constants that are module children' do
+ expect(subject.constants)
+ .to include QA::Page::Project::New, QA::Page::Project::Settings
+ end
+ end
+
+ describe '#descendants' do
+ subject do
+ described_class.new(QA::Page::Project)
+ end
+
+ it 'recursively returns all descendants that are page objects' do
+ expect(subject.descendants)
+ .to include QA::Page::Project::New, QA::Page::Project::Settings::Repository
+ end
+
+ it 'does not return modules that aggregate page objects' do
+ expect(subject.descendants)
+ .not_to include QA::Page::Project::Settings
+ end
+ end
+
+ context 'when checking validation errors' do
+ let(:view) { spy('view') }
+
+ before do
+ allow(QA::Page::Admin::Settings)
+ .to receive(:views).and_return([view])
+ end
+
+ subject do
+ described_class.new(QA::Page::Admin)
+ end
+
+ context 'when there are no validation errors' do
+ before do
+ allow(view).to receive(:errors).and_return([])
+ end
+
+ describe '#errors' do
+ it 'does not return errors' do
+ expect(subject.errors).to be_empty
+ end
+ end
+
+ describe '#validate!' do
+ it 'does not raise error' do
+ expect { subject.validate! }.not_to raise_error
+ end
+ end
+ end
+
+ context 'when there are validation errors' do
+ before do
+ allow(view).to receive(:errors)
+ .and_return(['some error', 'another error'])
+ end
+
+ describe '#errors' do
+ it 'returns errors' do
+ expect(subject.errors.count).to eq 2
+ end
+ end
+
+ describe '#validate!' do
+ it 'raises validation error' do
+ expect { subject.validate! }
+ .to raise_error described_class::ValidationError
+ end
+ end
+ end
+ end
+end
diff --git a/qa/spec/page/view_spec.rb b/qa/spec/page/view_spec.rb
new file mode 100644
index 00000000000..aedbc3863a7
--- /dev/null
+++ b/qa/spec/page/view_spec.rb
@@ -0,0 +1,70 @@
+describe QA::Page::View do
+ let(:element) do
+ double('element', name: :something, pattern: /some element/)
+ end
+
+ subject { described_class.new('some/file.html', [element]) }
+
+ describe '.evaluate' do
+ it 'evaluates a block and returns a DSL object' do
+ results = described_class.evaluate do
+ element :something, 'my pattern'
+ element :something_else, /another pattern/
+ end
+
+ expect(results.elements.size).to eq 2
+ end
+ end
+
+ describe '#pathname' do
+ it 'returns an absolute and clean path to the view' do
+ expect(subject.pathname.to_s).not_to include 'qa/page/'
+ expect(subject.pathname.to_s).to include 'some/file.html'
+ end
+ end
+
+ describe '#errors' do
+ context 'when view partial is present' do
+ before do
+ allow(subject.pathname).to receive(:readable?)
+ .and_return(true)
+ end
+
+ context 'when pattern is found' do
+ before do
+ allow(File).to receive(:foreach)
+ .and_yield('some element').once
+ allow(element).to receive(:matches?)
+ .with('some element').and_return(true)
+ end
+
+ it 'walks through the view and asserts on elements existence' do
+ expect(subject.errors).to be_empty
+ end
+ end
+
+ context 'when pattern has not been found' do
+ before do
+ allow(File).to receive(:foreach)
+ .and_yield('some element').once
+ allow(element).to receive(:matches?)
+ .with('some element').and_return(false)
+ end
+
+ it 'returns an array of errors related to missing elements' do
+ expect(subject.errors).not_to be_empty
+ expect(subject.errors.first)
+ .to match %r(Missing element `.*` in `.*/some/file.html` view)
+ end
+ end
+ end
+
+ context 'when view partial has not been found' do
+ it 'returns an error when it is not able to find the partial' do
+ expect(subject.errors).to be_one
+ expect(subject.errors.first)
+ .to match %r(Missing view partial `.*/some/file.html`!)
+ end
+ end
+ end
+end
diff --git a/qa/spec/scenario/test/sanity/selectors_spec.rb b/qa/spec/scenario/test/sanity/selectors_spec.rb
new file mode 100644
index 00000000000..45d21d54955
--- /dev/null
+++ b/qa/spec/scenario/test/sanity/selectors_spec.rb
@@ -0,0 +1,40 @@
+describe QA::Scenario::Test::Sanity::Selectors do
+ let(:validator) { spy('validator') }
+
+ before do
+ stub_const('QA::Page::Validator', validator)
+ end
+
+ context 'when there are errors detected' do
+ before do
+ allow(validator).to receive(:errors).and_return(['some error'])
+ end
+
+ it 'outputs information about errors' do
+ expect { described_class.perform }
+ .to output(/some error/).to_stderr
+
+ expect { described_class.perform }
+ .to output(/electors validation test detected problems/)
+ .to_stderr
+ end
+ end
+
+ context 'when there are no errors detected' do
+ before do
+ allow(validator).to receive(:errors).and_return([])
+ end
+
+ it 'processes pages module' do
+ described_class.perform
+
+ expect(validator).to have_received(:new).with(QA::Page)
+ end
+
+ it 'triggers validation' do
+ described_class.perform
+
+ expect(validator).to have_received(:validate!).at_least(:once)
+ end
+ end
+end
diff --git a/spec/controllers/projects/clusters/gcp_controller_spec.rb b/spec/controllers/projects/clusters/gcp_controller_spec.rb
index be19fa93183..775f9db1c6e 100644
--- a/spec/controllers/projects/clusters/gcp_controller_spec.rb
+++ b/spec/controllers/projects/clusters/gcp_controller_spec.rb
@@ -137,11 +137,14 @@ describe Projects::Clusters::GcpController do
context 'when access token is valid' do
before do
stub_google_api_validate_token
+ allow_any_instance_of(described_class).to receive(:authorize_google_project_billing)
end
context 'when google project billing is enabled' do
before do
- stub_google_project_billing_status
+ redis_double = double
+ allow(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis_double)
+ allow(redis_double).to receive(:get).with(CheckGcpProjectBillingWorker.redis_shared_state_key_for('token')).and_return('true')
end
it 'creates a new cluster' do
@@ -158,7 +161,7 @@ describe Projects::Clusters::GcpController do
it 'renders the cluster form with an error' do
go
- expect(response).to set_flash[:error]
+ expect(response).to set_flash[:alert]
expect(response).to render_template('new')
end
end
diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb
index 523cc08496b..8953b30bebf 100644
--- a/spec/features/projects/clusters/gcp_spec.rb
+++ b/spec/features/projects/clusters/gcp_spec.rb
@@ -13,6 +13,8 @@ feature 'Gcp Cluster', :js do
end
context 'when user has signed with Google' do
+ let(:project_id) { 'test-project-1234' }
+
before do
allow_any_instance_of(Projects::Clusters::GcpController)
.to receive(:token_in_session).and_return('token')
@@ -23,7 +25,7 @@ feature 'Gcp Cluster', :js do
context 'when user has a GCP project with billing enabled' do
before do
allow_any_instance_of(Projects::Clusters::GcpController).to receive(:authorize_google_project_billing)
- stub_google_project_billing_status
+ allow_any_instance_of(Projects::Clusters::GcpController).to receive(:google_project_billing_status).and_return('true')
end
context 'when user does not have a cluster and visits cluster index page' do
@@ -131,15 +133,41 @@ feature 'Gcp Cluster', :js do
context 'when user does not have a GCP project with billing enabled' do
before do
+ allow_any_instance_of(Projects::Clusters::GcpController).to receive(:authorize_google_project_billing)
+ allow_any_instance_of(Projects::Clusters::GcpController).to receive(:google_project_billing_status).and_return('false')
+
visit project_clusters_path(project)
click_link 'Add cluster'
click_link 'Create on GKE'
+
+ fill_in 'cluster_provider_gcp_attributes_gcp_project_id', with: 'gcp-project-123'
+ fill_in 'cluster_name', with: 'dev-cluster'
+ click_button 'Create cluster'
+ end
+
+ it 'user sees form with error' do
+ expect(page).to have_content('Please enable billing for one of your projects to be able to create a cluster, then try again.')
+ end
+ end
+
+ context 'when gcp billing status is not in redis' do
+ before do
+ allow_any_instance_of(Projects::Clusters::GcpController).to receive(:authorize_google_project_billing)
+ allow_any_instance_of(Projects::Clusters::GcpController).to receive(:google_project_billing_status).and_return(nil)
+
+ visit project_clusters_path(project)
+
+ click_link 'Add cluster'
+ click_link 'Create on GKE'
+
+ fill_in 'cluster_provider_gcp_attributes_gcp_project_id', with: 'gcp-project-123'
+ fill_in 'cluster_name', with: 'dev-cluster'
+ click_button 'Create cluster'
end
- it 'user sees a check page' do
- pending 'the frontend still has not been implemented'
- expect(page).to have_link('Continue')
+ it 'user sees form with error' do
+ expect(page).to have_content('We could not verify that one of your projects on GCP has billing enabled. Please try again.')
end
end
end
diff --git a/spec/features/projects/commits/user_browses_commits_spec.rb b/spec/features/projects/commits/user_browses_commits_spec.rb
index 41f3c15a94c..b650c1f4197 100644
--- a/spec/features/projects/commits/user_browses_commits_spec.rb
+++ b/spec/features/projects/commits/user_browses_commits_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'User broweses commits' do
+describe 'User browses commits' do
let(:user) { create(:user) }
let(:project) { create(:project, :repository, namespace: user.namespace) }
@@ -31,6 +31,19 @@ describe 'User broweses commits' do
check_author_link(RepoHelpers.sample_commit.author_email, user)
end
end
+
+ context 'when the blob does not exist' do
+ let(:commit) { create(:commit, project: project) }
+
+ it 'shows a blank label' do
+ allow_any_instance_of(Gitlab::Diff::File).to receive(:blob).and_return(nil)
+ allow_any_instance_of(Gitlab::Diff::File).to receive(:raw_binary?).and_return(true)
+
+ visit(project_commit_path(project, commit))
+
+ expect(find('.diff-file-changes', visible: false)).to have_content('No file name available')
+ end
+ end
end
private
diff --git a/spec/features/projects/tree/create_directory_spec.rb b/spec/features/projects/tree/create_directory_spec.rb
index 3f6d16c8acf..0c67196f53e 100644
--- a/spec/features/projects/tree/create_directory_spec.rb
+++ b/spec/features/projects/tree/create_directory_spec.rb
@@ -14,7 +14,7 @@ feature 'Multi-file editor new directory', :js do
wait_for_requests
- click_link('Multi Edit')
+ click_link('Web IDE')
wait_for_requests
end
diff --git a/spec/features/projects/tree/create_file_spec.rb b/spec/features/projects/tree/create_file_spec.rb
index ba71eef07f4..85f7318c05d 100644
--- a/spec/features/projects/tree/create_file_spec.rb
+++ b/spec/features/projects/tree/create_file_spec.rb
@@ -14,7 +14,7 @@ feature 'Multi-file editor new file', :js do
wait_for_requests
- click_link('Multi Edit')
+ click_link('Web IDE')
wait_for_requests
end
diff --git a/spec/features/projects/tree/upload_file_spec.rb b/spec/features/projects/tree/upload_file_spec.rb
index 9fbb1dbd0e8..f81e8677e92 100644
--- a/spec/features/projects/tree/upload_file_spec.rb
+++ b/spec/features/projects/tree/upload_file_spec.rb
@@ -16,7 +16,7 @@ feature 'Multi-file editor upload file', :js do
wait_for_requests
- click_link('Multi Edit')
+ click_link('Web IDE')
wait_for_requests
end
diff --git a/spec/fixtures/api/schemas/public_api/v4/pipelines.json b/spec/fixtures/api/schemas/public_api/v4/pipelines.json
new file mode 100644
index 00000000000..8b08a00f708
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/pipelines.json
@@ -0,0 +1,4 @@
+{
+ "type": "array",
+ "items": { "$ref": "pipeline/basic.json" }
+}
diff --git a/spec/initializers/gollum_spec.rb b/spec/initializers/gollum_spec.rb
new file mode 100644
index 00000000000..adf824a8947
--- /dev/null
+++ b/spec/initializers/gollum_spec.rb
@@ -0,0 +1,62 @@
+require 'spec_helper'
+
+describe 'gollum' do
+ let(:project) { create(:project) }
+ let(:user) { project.owner }
+ let(:wiki) { ProjectWiki.new(project, user) }
+ let(:gollum_wiki) { Gollum::Wiki.new(wiki.repository.path) }
+
+ before do
+ create_page(page_name, 'content1')
+ end
+
+ after do
+ destroy_page(page_name)
+ end
+
+ context 'with simple paths' do
+ let(:page_name) { 'page1' }
+
+ it 'returns the entry hash if it matches the file name' do
+ expect(tree_entry(page_name)).not_to be_nil
+ end
+
+ it 'returns nil if the path does not fit completely' do
+ expect(tree_entry("foo/#{page_name}")).to be_nil
+ end
+ end
+
+ context 'with complex paths' do
+ let(:page_name) { '/foo/bar/page2' }
+
+ it 'returns the entry hash if it matches the file name' do
+ expect(tree_entry(page_name)).not_to be_nil
+ end
+
+ it 'returns nil if the path does not fit completely' do
+ expect(tree_entry("foo1/bar/page2")).to be_nil
+ expect(tree_entry("foo/bar1/page2")).to be_nil
+ end
+ end
+
+ def tree_entry(name)
+ gollum_wiki.repo.git.tree_entry(wiki_commits[0].commit, name + '.md')
+ end
+
+ def wiki_commits
+ gollum_wiki.repo.commits
+ end
+
+ def commit_details
+ Gitlab::Git::Wiki::CommitDetails.new(user.name, user.email, "test commit")
+ end
+
+ def create_page(name, content)
+ wiki.wiki.write_page(name, :markdown, content, commit_details)
+ end
+
+ def destroy_page(name)
+ page = wiki.find_page(name).page
+ wiki.delete_page(page, "test commit")
+ end
+end
diff --git a/spec/javascripts/boards/issue_card_spec.js b/spec/javascripts/boards/issue_card_spec.js
index 8ef221257be..278155c585e 100644
--- a/spec/javascripts/boards/issue_card_spec.js
+++ b/spec/javascripts/boards/issue_card_spec.js
@@ -45,6 +45,9 @@ describe('Issue card component', () => {
component = new Vue({
el: document.querySelector('.test-container'),
+ components: {
+ 'issue-card': gl.issueBoards.IssueCardInner,
+ },
data() {
return {
list,
@@ -53,9 +56,6 @@ describe('Issue card component', () => {
rootPath: '/',
};
},
- components: {
- 'issue-card': gl.issueBoards.IssueCardInner,
- },
template: `
<issue-card
:issue="issue"
diff --git a/spec/javascripts/cycle_analytics/banner_spec.js b/spec/javascripts/cycle_analytics/banner_spec.js
index fb6b7fee168..64a76a6ee5f 100644
--- a/spec/javascripts/cycle_analytics/banner_spec.js
+++ b/spec/javascripts/cycle_analytics/banner_spec.js
@@ -20,8 +20,9 @@ describe('Cycle analytics banner', () => {
expect(
vm.$el.querySelector('h4').textContent.trim(),
).toEqual('Introducing Cycle Analytics');
+
expect(
- vm.$el.querySelector('p').textContent.trim(),
+ vm.$el.querySelector('p').textContent.trim().replace(/[\r\n]+/g, ' '),
).toContain('Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.');
expect(
vm.$el.querySelector('a').textContent.trim(),
diff --git a/spec/javascripts/cycle_analytics/total_time_component_spec.js b/spec/javascripts/cycle_analytics/total_time_component_spec.js
index 31b65fd1cde..ad0fc38a856 100644
--- a/spec/javascripts/cycle_analytics/total_time_component_spec.js
+++ b/spec/javascripts/cycle_analytics/total_time_component_spec.js
@@ -23,7 +23,7 @@ describe('Total time component', () => {
},
});
- expect(vm.$el.textContent.trim()).toEqual('3 days 4 hrs');
+ expect(vm.$el.textContent.trim().replace(/\s\s+/g, ' ')).toEqual('3 days 4 hrs');
});
it('should render information for hours and minutes', () => {
@@ -34,7 +34,7 @@ describe('Total time component', () => {
},
});
- expect(vm.$el.textContent.trim()).toEqual('4 hrs 35 mins');
+ expect(vm.$el.textContent.trim().replace(/\s\s+/g, ' ')).toEqual('4 hrs 35 mins');
});
it('should render information for seconds', () => {
@@ -44,7 +44,7 @@ describe('Total time component', () => {
},
});
- expect(vm.$el.textContent.trim()).toEqual('45 s');
+ expect(vm.$el.textContent.trim().replace(/\s\s+/g, ' ')).toEqual('45 s');
});
});
diff --git a/spec/javascripts/groups/components/app_spec.js b/spec/javascripts/groups/components/app_spec.js
index 97e39f6411b..8338efe915b 100644
--- a/spec/javascripts/groups/components/app_spec.js
+++ b/spec/javascripts/groups/components/app_spec.js
@@ -256,6 +256,36 @@ describe('AppComponent', () => {
});
});
+ describe('showLeaveGroupModal', () => {
+ it('caches candidate group (as props) which is to be left', () => {
+ const group = Object.assign({}, mockParentGroupItem);
+ expect(vm.targetGroup).toBe(null);
+ expect(vm.targetParentGroup).toBe(null);
+ vm.showLeaveGroupModal(group, mockParentGroupItem);
+ expect(vm.targetGroup).not.toBe(null);
+ expect(vm.targetParentGroup).not.toBe(null);
+ });
+
+ it('updates props which show modal confirmation dialog', () => {
+ const group = Object.assign({}, mockParentGroupItem);
+ expect(vm.showModal).toBeFalsy();
+ expect(vm.groupLeaveConfirmationMessage).toBe('');
+ vm.showLeaveGroupModal(group, mockParentGroupItem);
+ expect(vm.showModal).toBeTruthy();
+ expect(vm.groupLeaveConfirmationMessage).toBe(`Are you sure you want to leave the "${group.fullName}" group?`);
+ });
+ });
+
+ describe('hideLeaveGroupModal', () => {
+ it('hides modal confirmation which is shown before leaving the group', () => {
+ const group = Object.assign({}, mockParentGroupItem);
+ vm.showLeaveGroupModal(group, mockParentGroupItem);
+ expect(vm.showModal).toBeTruthy();
+ vm.hideLeaveGroupModal();
+ expect(vm.showModal).toBeFalsy();
+ });
+ });
+
describe('leaveGroup', () => {
let groupItem;
let childGroupItem;
@@ -265,21 +295,24 @@ describe('AppComponent', () => {
groupItem.children = mockChildren;
childGroupItem = groupItem.children[0];
groupItem.isChildrenLoading = false;
+ vm.targetGroup = childGroupItem;
+ vm.targetParentGroup = groupItem;
});
- it('should leave group and remove group item from tree', (done) => {
+ it('hides modal confirmation leave group and remove group item from tree', (done) => {
const notice = `You left the "${childGroupItem.fullName}" group.`;
spyOn(vm.service, 'leaveGroup').and.returnValue(returnServicePromise({ notice }));
spyOn(vm.store, 'removeGroup').and.callThrough();
spyOn(window, 'Flash');
spyOn($, 'scrollTo');
- vm.leaveGroup(childGroupItem, groupItem);
- expect(childGroupItem.isBeingRemoved).toBeTruthy();
- expect(vm.service.leaveGroup).toHaveBeenCalledWith(childGroupItem.leavePath);
+ vm.leaveGroup();
+ expect(vm.showModal).toBeFalsy();
+ expect(vm.targetGroup.isBeingRemoved).toBeTruthy();
+ expect(vm.service.leaveGroup).toHaveBeenCalledWith(vm.targetGroup.leavePath);
setTimeout(() => {
expect($.scrollTo).toHaveBeenCalledWith(0);
- expect(vm.store.removeGroup).toHaveBeenCalledWith(childGroupItem, groupItem);
+ expect(vm.store.removeGroup).toHaveBeenCalledWith(vm.targetGroup, vm.targetParentGroup);
expect(window.Flash).toHaveBeenCalledWith(notice, 'notice');
done();
}, 0);
@@ -291,13 +324,13 @@ describe('AppComponent', () => {
spyOn(vm.store, 'removeGroup').and.callThrough();
spyOn(window, 'Flash');
- vm.leaveGroup(childGroupItem, groupItem);
- expect(childGroupItem.isBeingRemoved).toBeTruthy();
+ vm.leaveGroup();
+ expect(vm.targetGroup.isBeingRemoved).toBeTruthy();
expect(vm.service.leaveGroup).toHaveBeenCalledWith(childGroupItem.leavePath);
setTimeout(() => {
expect(vm.store.removeGroup).not.toHaveBeenCalled();
expect(window.Flash).toHaveBeenCalledWith(message);
- expect(childGroupItem.isBeingRemoved).toBeFalsy();
+ expect(vm.targetGroup.isBeingRemoved).toBeFalsy();
done();
}, 0);
});
@@ -309,12 +342,12 @@ describe('AppComponent', () => {
spyOn(window, 'Flash');
vm.leaveGroup(childGroupItem, groupItem);
- expect(childGroupItem.isBeingRemoved).toBeTruthy();
+ expect(vm.targetGroup.isBeingRemoved).toBeTruthy();
expect(vm.service.leaveGroup).toHaveBeenCalledWith(childGroupItem.leavePath);
setTimeout(() => {
expect(vm.store.removeGroup).not.toHaveBeenCalled();
expect(window.Flash).toHaveBeenCalledWith(message);
- expect(childGroupItem.isBeingRemoved).toBeFalsy();
+ expect(vm.targetGroup.isBeingRemoved).toBeFalsy();
done();
}, 0);
});
@@ -364,7 +397,7 @@ describe('AppComponent', () => {
Vue.nextTick(() => {
expect(eventHub.$on).toHaveBeenCalledWith('fetchPage', jasmine.any(Function));
expect(eventHub.$on).toHaveBeenCalledWith('toggleChildren', jasmine.any(Function));
- expect(eventHub.$on).toHaveBeenCalledWith('leaveGroup', jasmine.any(Function));
+ expect(eventHub.$on).toHaveBeenCalledWith('showLeaveGroupModal', jasmine.any(Function));
expect(eventHub.$on).toHaveBeenCalledWith('updatePagination', jasmine.any(Function));
expect(eventHub.$on).toHaveBeenCalledWith('updateGroups', jasmine.any(Function));
newVm.$destroy();
@@ -404,7 +437,7 @@ describe('AppComponent', () => {
Vue.nextTick(() => {
expect(eventHub.$off).toHaveBeenCalledWith('fetchPage', jasmine.any(Function));
expect(eventHub.$off).toHaveBeenCalledWith('toggleChildren', jasmine.any(Function));
- expect(eventHub.$off).toHaveBeenCalledWith('leaveGroup', jasmine.any(Function));
+ expect(eventHub.$off).toHaveBeenCalledWith('showLeaveGroupModal', jasmine.any(Function));
expect(eventHub.$off).toHaveBeenCalledWith('updatePagination', jasmine.any(Function));
expect(eventHub.$off).toHaveBeenCalledWith('updateGroups', jasmine.any(Function));
done();
@@ -439,5 +472,14 @@ describe('AppComponent', () => {
done();
});
});
+
+ it('renders modal confirmation dialog', () => {
+ vm.groupLeaveConfirmationMessage = 'Are you sure you want to leave the "foo" group?';
+ vm.showModal = true;
+ const modalDialogEl = vm.$el.querySelector('.modal');
+ expect(modalDialogEl).not.toBe(null);
+ expect(modalDialogEl.querySelector('.modal-title').innerText.trim()).toBe('Are you sure?');
+ expect(modalDialogEl.querySelector('.btn.btn-warning').innerText.trim()).toBe('Leave');
+ });
});
});
diff --git a/spec/javascripts/groups/components/item_actions_spec.js b/spec/javascripts/groups/components/item_actions_spec.js
index 6d6fb410859..acccbe639c4 100644
--- a/spec/javascripts/groups/components/item_actions_spec.js
+++ b/spec/javascripts/groups/components/item_actions_spec.js
@@ -26,32 +26,12 @@ describe('ItemActionsComponent', () => {
vm.$destroy();
});
- describe('computed', () => {
- describe('leaveConfirmationMessage', () => {
- it('should return appropriate string for leave group confirmation', () => {
- expect(vm.leaveConfirmationMessage).toBe('Are you sure you want to leave the "platform / hardware" group?');
- });
- });
- });
-
describe('methods', () => {
describe('onLeaveGroup', () => {
- it('should change `modalStatus` prop to `true` which shows confirmation dialog', () => {
- expect(vm.modalStatus).toBeFalsy();
- vm.onLeaveGroup();
- expect(vm.modalStatus).toBeTruthy();
- });
- });
-
- describe('leaveGroup', () => {
- it('should change `modalStatus` prop to `false` and emit `leaveGroup` event with required params when called with `leaveConfirmed` as `true`', () => {
+ it('emits `showLeaveGroupModal` event with `group` and `parentGroup` props', () => {
spyOn(eventHub, '$emit');
- vm.modalStatus = true;
-
- vm.leaveGroup();
-
- expect(vm.modalStatus).toBeFalsy();
- expect(eventHub.$emit).toHaveBeenCalledWith('leaveGroup', vm.group, vm.parentGroup);
+ vm.onLeaveGroup();
+ expect(eventHub.$emit).toHaveBeenCalledWith('showLeaveGroupModal', vm.group, vm.parentGroup);
});
});
});
@@ -72,7 +52,8 @@ describe('ItemActionsComponent', () => {
expect(editBtn.getAttribute('href')).toBe(group.editPath);
expect(editBtn.getAttribute('aria-label')).toBe('Edit group');
expect(editBtn.dataset.originalTitle).toBe('Edit group');
- expect(editBtn.querySelector('i.fa.fa-cogs')).toBeDefined();
+ expect(editBtn.querySelectorAll('svg use').length).not.toBe(0);
+ expect(editBtn.querySelector('svg use').getAttribute('xlink:href')).toContain('#settings');
newVm.$destroy();
});
@@ -88,17 +69,10 @@ describe('ItemActionsComponent', () => {
expect(leaveBtn.getAttribute('href')).toBe(group.leavePath);
expect(leaveBtn.getAttribute('aria-label')).toBe('Leave this group');
expect(leaveBtn.dataset.originalTitle).toBe('Leave this group');
- expect(leaveBtn.querySelector('i.fa.fa-sign-out')).toBeDefined();
+ expect(leaveBtn.querySelectorAll('svg use').length).not.toBe(0);
+ expect(leaveBtn.querySelector('svg use').getAttribute('xlink:href')).toContain('#leave');
newVm.$destroy();
});
-
- it('should show modal dialog when `modalStatus` is set to `true`', () => {
- vm.modalStatus = true;
- const modalDialogEl = vm.$el.querySelector('.modal');
- expect(modalDialogEl).toBeDefined();
- expect(modalDialogEl.querySelector('.modal-title').innerText.trim()).toBe('Are you sure?');
- expect(modalDialogEl.querySelector('.btn.btn-warning').innerText.trim()).toBe('Leave');
- });
});
});
diff --git a/spec/javascripts/oauth_remember_me_spec.js b/spec/javascripts/oauth_remember_me_spec.js
index f90e0093d25..b24563f738b 100644
--- a/spec/javascripts/oauth_remember_me_spec.js
+++ b/spec/javascripts/oauth_remember_me_spec.js
@@ -1,4 +1,4 @@
-import OAuthRememberMe from '~/oauth_remember_me';
+import OAuthRememberMe from '~/pages/sessions/new/oauth_remember_me';
describe('OAuthRememberMe', () => {
preloadFixtures('static/oauth_remember_me.html.raw');
diff --git a/spec/javascripts/abuse_reports_spec.js b/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js
index 7f6b5873011..d2386077aa6 100644
--- a/spec/javascripts/abuse_reports_spec.js
+++ b/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js
@@ -1,5 +1,5 @@
import '~/lib/utils/text_utility';
-import AbuseReports from '~/abuse_reports';
+import AbuseReports from '~/pages/admin/abuse_reports/abuse_reports';
describe('Abuse Reports', () => {
const FIXTURE = 'abuse_reports/abuse_reports_list.html.raw';
diff --git a/spec/javascripts/pipelines/empty_state_spec.js b/spec/javascripts/pipelines/empty_state_spec.js
index 6611b74594f..97f04844b3a 100644
--- a/spec/javascripts/pipelines/empty_state_spec.js
+++ b/spec/javascripts/pipelines/empty_state_spec.js
@@ -24,11 +24,11 @@ describe('Pipelines Empty State', () => {
expect(component.$el.querySelector('h4').textContent).toContain('Build with confidence');
expect(
- component.$el.querySelector('p').textContent,
+ component.$el.querySelector('p').textContent.trim().replace(/[\r\n]+/g, ' '),
).toContain('Continous Integration can help catch bugs by running your tests automatically');
expect(
- component.$el.querySelector('p').textContent,
+ component.$el.querySelector('p').textContent.trim().replace(/[\r\n]+/g, ' '),
).toContain('Continuous Deployment can help you deliver code to your product environment');
});
diff --git a/spec/javascripts/registry/components/app_spec.js b/spec/javascripts/registry/components/app_spec.js
index 43e7d9e1224..87259fe0bab 100644
--- a/spec/javascripts/registry/components/app_spec.js
+++ b/spec/javascripts/registry/components/app_spec.js
@@ -89,7 +89,7 @@ describe('Registry List', () => {
it('should render empty message', (done) => {
setTimeout(() => {
expect(
- vm.$el.querySelector('p').textContent.trim(),
+ vm.$el.querySelector('p').textContent.trim().replace(/[\r\n]+/g, ' '),
).toEqual('No container images stored for this project. Add one by following the instructions above.');
done();
}, 0);
diff --git a/spec/javascripts/signin_tabs_memoizer_spec.js b/spec/javascripts/signin_tabs_memoizer_spec.js
index a53e8a94d89..c4f500788b2 100644
--- a/spec/javascripts/signin_tabs_memoizer_spec.js
+++ b/spec/javascripts/signin_tabs_memoizer_spec.js
@@ -1,5 +1,5 @@
import AccessorUtilities from '~/lib/utils/accessor';
-import SigninTabsMemoizer from '~/signin_tabs_memoizer';
+import SigninTabsMemoizer from '~/pages/sessions/new/signin_tabs_memoizer';
(() => {
describe('SigninTabsMemoizer', () => {
diff --git a/spec/javascripts/vue_shared/components/clipboard_button_spec.js b/spec/javascripts/vue_shared/components/clipboard_button_spec.js
new file mode 100644
index 00000000000..08e4e1f8337
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/clipboard_button_spec.js
@@ -0,0 +1,31 @@
+import Vue from 'vue';
+import clipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+describe('clipboard button', () => {
+ let vm;
+
+ beforeEach(() => {
+ const Component = Vue.extend(clipboardButton);
+ vm = mountComponent(Component, {
+ text: 'copy me',
+ title: 'Copy this value into Clipboard!',
+ });
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders a button for clipboard', () => {
+ expect(vm.$el.tagName).toEqual('BUTTON');
+ expect(vm.$el.getAttribute('data-clipboard-text')).toEqual('copy me');
+ expect(vm.$el.querySelector('i').className).toEqual('fa fa-clipboard');
+ });
+
+ it('should have a tooltip with default values', () => {
+ expect(vm.$el.getAttribute('data-original-title')).toEqual('Copy this value into Clipboard!');
+ expect(vm.$el.getAttribute('data-placement')).toEqual('top');
+ expect(vm.$el.getAttribute('data-container')).toEqual(null);
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/markdown/field_spec.js b/spec/javascripts/vue_shared/components/markdown/field_spec.js
index 24209be83fe..5f980bbf36c 100644
--- a/spec/javascripts/vue_shared/components/markdown/field_spec.js
+++ b/spec/javascripts/vue_shared/components/markdown/field_spec.js
@@ -12,14 +12,14 @@ describe('Markdown field component', () => {
beforeEach((done) => {
vm = new Vue({
+ components: {
+ fieldComponent,
+ },
data() {
return {
text: 'testing\n123',
};
},
- components: {
- fieldComponent,
- },
template: `
<field-component
markdown-preview-path="/preview"
diff --git a/spec/javascripts/vue_shared/components/table_pagination_spec.js b/spec/javascripts/vue_shared/components/table_pagination_spec.js
index b3b5dd1d10a..c63f15e5880 100644
--- a/spec/javascripts/vue_shared/components/table_pagination_spec.js
+++ b/spec/javascripts/vue_shared/components/table_pagination_spec.js
@@ -72,7 +72,6 @@ describe('Pagination component', () => {
});
component.$el.querySelector('.js-previous-button a').click();
-
expect(spy).toHaveBeenCalledWith(1);
});
});
diff --git a/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb b/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb
new file mode 100644
index 00000000000..21a791f5695
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb
@@ -0,0 +1,50 @@
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::AddMergeRequestDiffCommitsCount, :migration, schema: 20180105212544 do
+ let(:projects_table) { table(:projects) }
+ let(:merge_requests_table) { table(:merge_requests) }
+ let(:merge_request_diffs_table) { table(:merge_request_diffs) }
+ let(:merge_request_diff_commits_table) { table(:merge_request_diff_commits) }
+
+ let(:project) { projects_table.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce') }
+ let(:merge_request) do
+ merge_requests_table.create!(target_project_id: project.id,
+ target_branch: 'master',
+ source_project_id: project.id,
+ source_branch: 'mr name',
+ title: 'mr name')
+ end
+
+ def create_diff!(name, commits: 0)
+ mr_diff = merge_request_diffs_table.create!(
+ merge_request_id: merge_request.id)
+
+ commits.times do |i|
+ merge_request_diff_commits_table.create!(
+ merge_request_diff_id: mr_diff.id,
+ relative_order: i, sha: i)
+ end
+
+ mr_diff
+ end
+
+ describe '#perform' do
+ it 'migrates diffs that have no commits' do
+ diff = create_diff!('with_multiple_commits', commits: 0)
+
+ subject.perform(diff.id, diff.id)
+
+ expect(diff.reload.commits_count).to eq(0)
+ end
+
+ it 'migrates multiple diffs to the correct values' do
+ diffs = Array.new(3).map.with_index { |_, i| create_diff!(i, commits: 3) }
+
+ subject.perform(diffs.first.id, diffs.last.id)
+
+ diffs.each do |diff|
+ expect(diff.reload.commits_count).to eq(3)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb
index 84d9e635810..98730602863 100644
--- a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb
+++ b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb
@@ -10,6 +10,11 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :t
let(:merge_request_diff) { MergeRequest.find(merge_request.id).create_merge_request_diff }
let(:updated_merge_request_diff) { MergeRequestDiff.find(merge_request_diff.id) }
+ before do
+ allow_any_instance_of(MergeRequestDiff)
+ .to receive(:commits_count=).and_return(nil)
+ end
+
def diffs_to_hashes(diffs)
diffs.as_json(only: Gitlab::Git::Diff::SERIALIZE_KEYS).map(&:with_indifferent_access)
end
diff --git a/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb b/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb
index dfe3b31f1c0..e99257e3481 100644
--- a/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb
@@ -1,6 +1,12 @@
require 'rails_helper'
describe Gitlab::BackgroundMigration::PopulateMergeRequestMetricsWithEventsData, :migration, schema: 20171128214150 do
+ # commits_count attribute is added in a next migration
+ before do
+ allow_any_instance_of(MergeRequestDiff)
+ .to receive(:commits_count=).and_return(nil)
+ end
+
describe '#perform' do
let(:mr_with_event) { create(:merge_request) }
let!(:merged_event) { create(:event, :merged, target: mr_with_event) }
diff --git a/spec/lib/gitlab/bare_repository_import/importer_spec.rb b/spec/lib/gitlab/bare_repository_import/importer_spec.rb
index b5d86df09d2..f302e412a6e 100644
--- a/spec/lib/gitlab/bare_repository_import/importer_spec.rb
+++ b/spec/lib/gitlab/bare_repository_import/importer_spec.rb
@@ -74,14 +74,18 @@ describe Gitlab::BareRepositoryImport::Importer, repository: true do
importer.create_project_if_needed
end
- it 'creates the Git repo in disk' do
+ it 'creates the Git repo on disk with the proper symlink for hooks' do
create_bare_repository("#{project_path}.git")
importer.create_project_if_needed
project = Project.find_by_full_path(project_path)
+ repo_path = File.join(project.repository_storage_path, project.disk_path + '.git')
+ hook_path = File.join(repo_path, 'hooks')
- expect(File).to exist(File.join(project.repository_storage_path, project.disk_path + '.git'))
+ expect(File).to exist(repo_path)
+ expect(File.symlink?(hook_path)).to be true
+ expect(File.readlink(hook_path)).to eq(Gitlab.config.gitlab_shell.hooks_path)
end
context 'hashed storage enabled' do
diff --git a/spec/lib/gitlab/git/gitlab_projects_spec.rb b/spec/lib/gitlab/git/gitlab_projects_spec.rb
index a798b188a0d..beef843537d 100644
--- a/spec/lib/gitlab/git/gitlab_projects_spec.rb
+++ b/spec/lib/gitlab/git/gitlab_projects_spec.rb
@@ -25,51 +25,6 @@ describe Gitlab::Git::GitlabProjects do
it { expect(gl_projects.logger).to eq(logger) }
end
- describe '#mv_project' do
- let(:new_repo_path) { File.join(tmp_repos_path, 'repo.git') }
-
- it 'moves a repo directory' do
- expect(File.exist?(tmp_repo_path)).to be_truthy
-
- message = "Moving repository from <#{tmp_repo_path}> to <#{new_repo_path}>."
- expect(logger).to receive(:info).with(message)
-
- expect(gl_projects.mv_project('repo.git')).to be_truthy
-
- expect(File.exist?(tmp_repo_path)).to be_falsy
- expect(File.exist?(new_repo_path)).to be_truthy
- end
-
- it "fails if the source path doesn't exist" do
- expected_source_path = File.join(tmp_repos_path, 'bad-src.git')
- expect(logger).to receive(:error).with("mv-project failed: source path <#{expected_source_path}> does not exist.")
-
- result = build_gitlab_projects(tmp_repos_path, 'bad-src.git').mv_project('repo.git')
- expect(result).to be_falsy
- end
-
- it 'fails if the destination path already exists' do
- FileUtils.mkdir_p(File.join(tmp_repos_path, 'already-exists.git'))
-
- expected_distination_path = File.join(tmp_repos_path, 'already-exists.git')
- message = "mv-project failed: destination path <#{expected_distination_path}> already exists."
- expect(logger).to receive(:error).with(message)
-
- expect(gl_projects.mv_project('already-exists.git')).to be_falsy
- end
- end
-
- describe '#rm_project' do
- it 'removes a repo directory' do
- expect(File.exist?(tmp_repo_path)).to be_truthy
- expect(logger).to receive(:info).with("Removing repository <#{tmp_repo_path}>.")
-
- expect(gl_projects.rm_project).to be_truthy
-
- expect(File.exist?(tmp_repo_path)).to be_falsy
- end
- end
-
describe '#push_branches' do
let(:remote_name) { 'remote-name' }
let(:branch_name) { 'master' }
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index ec577903eb5..c5bf23e65b3 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -180,6 +180,7 @@ MergeRequestDiff:
- real_size
- head_commit_sha
- start_commit_sha
+- commits_count
MergeRequestDiffCommit:
- merge_request_diff_id
- relative_order
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index aed4855906e..2b61ce38418 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -443,32 +443,44 @@ describe Gitlab::Shell do
end
describe '#remove_repository' do
- subject { gitlab_shell.remove_repository(project.repository_storage_path, project.disk_path) }
+ let!(:project) { create(:project, :repository) }
+ let(:disk_path) { "#{project.disk_path}.git" }
it 'returns true when the command succeeds' do
- expect(gitlab_projects).to receive(:rm_project) { true }
+ expect(gitlab_shell.exists?(project.repository_storage_path, disk_path)).to be(true)
- is_expected.to be_truthy
+ expect(gitlab_shell.remove_repository(project.repository_storage_path, project.disk_path)).to be(true)
+
+ expect(gitlab_shell.exists?(project.repository_storage_path, disk_path)).to be(false)
end
- it 'returns false when the command fails' do
- expect(gitlab_projects).to receive(:rm_project) { false }
+ it 'keeps the namespace directory' do
+ gitlab_shell.remove_repository(project.repository_storage_path, project.disk_path)
- is_expected.to be_falsy
+ expect(gitlab_shell.exists?(project.repository_storage_path, disk_path)).to be(false)
+ expect(gitlab_shell.exists?(project.repository_storage_path, project.disk_path.gsub(project.name, ''))).to be(true)
end
end
describe '#mv_repository' do
+ let!(:project2) { create(:project, :repository) }
+
it 'returns true when the command succeeds' do
- expect(gitlab_projects).to receive(:mv_project).with('project/newpath.git') { true }
+ old_path = project2.disk_path
+ new_path = "project/new_path"
+
+ expect(gitlab_shell.exists?(project2.repository_storage_path, "#{old_path}.git")).to be(true)
+ expect(gitlab_shell.exists?(project2.repository_storage_path, "#{new_path}.git")).to be(false)
- expect(gitlab_shell.mv_repository(project.repository_storage_path, project.disk_path, 'project/newpath')).to be_truthy
+ expect(gitlab_shell.mv_repository(project2.repository_storage_path, old_path, new_path)).to be_truthy
+
+ expect(gitlab_shell.exists?(project2.repository_storage_path, "#{old_path}.git")).to be(false)
+ expect(gitlab_shell.exists?(project2.repository_storage_path, "#{new_path}.git")).to be(true)
end
it 'returns false when the command fails' do
- expect(gitlab_projects).to receive(:mv_project).with('project/newpath.git') { false }
-
- expect(gitlab_shell.mv_repository(project.repository_storage_path, project.disk_path, 'project/newpath')).to be_falsy
+ expect(gitlab_shell.mv_repository(project2.repository_storage_path, project2.disk_path, '')).to be_falsy
+ expect(gitlab_shell.exists?(project2.repository_storage_path, "#{project2.disk_path}.git")).to be(true)
end
end
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index 249c77dc636..2e7a0265a0b 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -26,11 +26,16 @@ describe Gitlab::Workhorse do
'GitalyRepository' => repository.gitaly_repository.to_h.deep_stringify_keys
)
end
+ let(:cache_disabled) { false }
subject do
described_class.send_git_archive(repository, ref: ref, format: format)
end
+ before do
+ allow(described_class).to receive(:git_archive_cache_disabled?).and_return(cache_disabled)
+ end
+
context 'when Gitaly workhorse_archive feature is enabled' do
it 'sets the header correctly' do
key, command, params = decode_workhorse_header(subject)
@@ -39,6 +44,15 @@ describe Gitlab::Workhorse do
expect(command).to eq('git-archive')
expect(params).to include(gitaly_params)
end
+
+ context 'when archive caching is disabled' do
+ let(:cache_disabled) { true }
+
+ it 'tells workhorse not to use the cache' do
+ _, _, params = decode_workhorse_header(subject)
+ expect(params).to include({ 'DisableCache' => true })
+ end
+ end
end
context 'when Gitaly workhorse_archive feature is disabled', :skip_gitaly_mock do
diff --git a/spec/migrations/schedule_populate_merge_request_metrics_with_events_data_spec.rb b/spec/migrations/schedule_populate_merge_request_metrics_with_events_data_spec.rb
index 2e6b2cff0ab..7494624066a 100644
--- a/spec/migrations/schedule_populate_merge_request_metrics_with_events_data_spec.rb
+++ b/spec/migrations/schedule_populate_merge_request_metrics_with_events_data_spec.rb
@@ -2,6 +2,12 @@ require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20171128214150_schedule_populate_merge_request_metrics_with_events_data.rb')
describe SchedulePopulateMergeRequestMetricsWithEventsData, :migration, :sidekiq do
+ # commits_count attribute is added in a next migration
+ before do
+ allow_any_instance_of(MergeRequestDiff)
+ .to receive(:commits_count=).and_return(nil)
+ end
+
let!(:mrs) { create_list(:merge_request, 3) }
it 'correctly schedules background migrations' do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 00afa09f1a3..78223c44999 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -1871,9 +1871,8 @@ describe Project do
end
it 'creates the new reference with rugged' do
- expect(project.repository.rugged.references).to receive(:create).with('HEAD',
- "refs/heads/#{project.default_branch}",
- force: true)
+ expect(project.repository.raw_repository).to receive(:write_ref).with('HEAD', "refs/heads/#{project.default_branch}", shell: false)
+
project.change_head(project.default_branch)
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index c0db2c1b386..edd981752d9 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -412,6 +412,28 @@ describe Repository do
end
end
+ describe '#create_hooks' do
+ let(:hook_path) { File.join(repository.path_to_repo, 'hooks') }
+
+ it 'symlinks the global hooks directory' do
+ repository.create_hooks
+
+ expect(File.symlink?(hook_path)).to be true
+ expect(File.readlink(hook_path)).to eq(Gitlab.config.gitlab_shell.hooks_path)
+ end
+
+ it 'replaces existing symlink with the right directory' do
+ FileUtils.mkdir_p(hook_path)
+
+ expect(File.symlink?(hook_path)).to be false
+
+ repository.create_hooks
+
+ expect(File.symlink?(hook_path)).to be true
+ expect(File.readlink(hook_path)).to eq(Gitlab.config.gitlab_shell.hooks_path)
+ end
+ end
+
describe "#create_dir" do
it "commits a change that creates a new directory" do
expect do
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 0c9fbb1f187..4eae3e50602 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -551,6 +551,49 @@ describe API::MergeRequests do
end
end
+ describe 'GET /projects/:id/merge_requests/:merge_request_iid/pipelines' do
+ context 'when authorized' do
+ let!(:pipeline) { create(:ci_empty_pipeline, project: project, user: user, ref: merge_request.source_branch, sha: merge_request.diff_head_sha) }
+ let!(:pipeline2) { create(:ci_empty_pipeline, project: project) }
+
+ it 'returns a paginated array of corresponding pipelines' do
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/pipelines")
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.count).to eq(1)
+ expect(json_response.first['id']).to eq(pipeline.id)
+ end
+
+ it 'exposes basic attributes' do
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/pipelines")
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/pipelines')
+ end
+
+ it 'returns 404 if MR does not exist' do
+ get api("/projects/#{project.id}/merge_requests/777/pipelines")
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ context 'when unauthorized' do
+ it 'returns 403' do
+ project = create(:project, public_builds: false)
+ merge_request = create(:merge_request, :simple, source_project: project)
+ guest = create(:user)
+ project.add_guest(guest)
+
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/pipelines", guest)
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+ end
+
describe "POST /projects/:id/merge_requests" do
context 'between branches projects' do
it "returns merge_request" do
diff --git a/spec/services/check_gcp_project_billing_service_spec.rb b/spec/services/check_gcp_project_billing_service_spec.rb
index f0e39ba6f49..3e68d906e71 100644
--- a/spec/services/check_gcp_project_billing_service_spec.rb
+++ b/spec/services/check_gcp_project_billing_service_spec.rb
@@ -1,29 +1,30 @@
require 'spec_helper'
describe CheckGcpProjectBillingService do
+ include GoogleApi::CloudPlatformHelpers
+
let(:service) { described_class.new }
- let(:projects) { [double(name: 'first_project'), double(name: 'second_project')] }
+ let(:project_id) { 'test-project-1234' }
describe '#execute' do
before do
- expect_any_instance_of(GoogleApi::CloudPlatform::Client)
- .to receive(:projects_list).and_return(projects)
-
- allow_any_instance_of(GoogleApi::CloudPlatform::Client)
- .to receive_message_chain(:projects_get_billing_info, :billingEnabled)
- .and_return(project_billing_enabled)
+ stub_cloud_platform_projects_list(project_id: project_id)
end
subject { service.execute('bogustoken') }
context 'google account has a billing enabled gcp project' do
- let(:project_billing_enabled) { true }
+ before do
+ stub_cloud_platform_projects_get_billing_info(project_id, true)
+ end
- it { is_expected.to eq(projects) }
+ it { is_expected.to all(satisfy { |project| project.project_id == project_id }) }
end
context 'google account does not have a billing enabled gcp project' do
- let(:project_billing_enabled) { false }
+ before do
+ stub_cloud_platform_projects_get_billing_info(project_id, false)
+ end
it { is_expected.to eq([]) }
end
diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb
index 53ea88332fb..f3c98fa5416 100644
--- a/spec/services/issues/move_service_spec.rb
+++ b/spec/services/issues/move_service_spec.rb
@@ -179,13 +179,15 @@ describe Issues::MoveService do
{ system: true, note: 'Some system note' },
{ system: false, note: 'Some comment 2' }]
end
-
+ let(:award_names) { %w(thumbsup thumbsdown facepalm) }
let(:notes_contents) { notes_params.map { |n| n[:note] } }
before do
note_params = { noteable: old_issue, project: old_project, author: author }
- notes_params.each do |note|
- create(:note, note_params.merge(note))
+ notes_params.each_with_index do |note, index|
+ new_note = create(:note, note_params.merge(note))
+ award_emoji_params = { awardable: new_note, name: award_names[index] }
+ create(:award_emoji, award_emoji_params)
end
end
@@ -199,6 +201,10 @@ describe Issues::MoveService do
expect(all_notes.pluck(:note).first(3)).to eq notes_contents
end
+ it 'creates new emojis for the new notes' do
+ expect(all_notes.map(&:award_emoji).to_a.flatten.map(&:name)).to eq award_names
+ end
+
it 'adds a system note about move after rewritten notes' do
expect(system_notes.last.note).to match /^moved from/
end
diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb
index a9605c6e4c6..cb4c3e72aa0 100644
--- a/spec/services/merge_requests/build_service_spec.rb
+++ b/spec/services/merge_requests/build_service_spec.rb
@@ -171,6 +171,24 @@ describe MergeRequests::BuildService do
end
end
end
+
+ context 'branch starts with external issue IID followed by a hyphen' do
+ let(:source_branch) { '12345-fix-issue' }
+
+ before do
+ allow(project).to receive(:external_issue_tracker).and_return(true)
+ end
+
+ it 'uses the title of the commit as the title of the merge request' do
+ expect(merge_request.title).to eq(commit_1.safe_message.split("\n").first)
+ end
+
+ it 'uses the description of the commit as the description of the merge request and appends the closes text' do
+ commit_description = commit_1.safe_message.split(/\n+/, 2).last
+
+ expect(merge_request.description).to eq("#{commit_description}\n\nCloses #12345")
+ end
+ end
end
context 'more than one commit in the diff' do
@@ -241,8 +259,12 @@ describe MergeRequests::BuildService do
allow(project).to receive(:external_issue_tracker).and_return(true)
end
- it 'sets the title to: Resolves External Issue $issue-iid' do
- expect(merge_request.title).to eq('Resolve External Issue 12345')
+ it 'sets the title to the humanized branch title' do
+ expect(merge_request.title).to eq('12345 fix issue')
+ end
+
+ it 'appends the closes text' do
+ expect(merge_request.description).to eq('Closes #12345')
end
end
end
diff --git a/spec/services/merge_requests/rebase_service_spec.rb b/spec/services/merge_requests/rebase_service_spec.rb
index d1b37cdd073..5f047e61c31 100644
--- a/spec/services/merge_requests/rebase_service_spec.rb
+++ b/spec/services/merge_requests/rebase_service_spec.rb
@@ -32,7 +32,7 @@ describe MergeRequests::RebaseService do
it 'returns an error' do
expect(service.execute(merge_request)).to match(status: :error,
- message: 'Failed to rebase. Should be done manually')
+ message: described_class::REBASE_ERROR)
end
end
@@ -41,15 +41,15 @@ describe MergeRequests::RebaseService do
allow(repository).to receive(:run_git!).and_raise('Something went wrong')
end
- it 'saves the error message' do
+ it 'saves a generic error message' do
subject.execute(merge_request)
- expect(merge_request.reload.merge_error).to eq 'Something went wrong'
+ expect(merge_request.reload.merge_error).to eq described_class::REBASE_ERROR
end
it 'returns an error' do
expect(service.execute(merge_request)).to match(status: :error,
- message: 'Failed to rebase. Should be done manually')
+ message: described_class::REBASE_ERROR)
end
end
@@ -58,15 +58,15 @@ describe MergeRequests::RebaseService do
allow(repository).to receive(:run_git!).and_raise(Gitlab::Git::Repository::GitError, 'Something went wrong')
end
- it 'saves the error message' do
+ it 'saves a generic error message' do
subject.execute(merge_request)
- expect(merge_request.reload.merge_error).to eq 'Something went wrong'
+ expect(merge_request.reload.merge_error).to eq described_class::REBASE_ERROR
end
it 'returns an error' do
expect(service.execute(merge_request)).to match(status: :error,
- message: 'Failed to rebase. Should be done manually')
+ message: described_class::REBASE_ERROR)
end
end
diff --git a/spec/support/google_api/cloud_platform_helpers.rb b/spec/support/google_api/cloud_platform_helpers.rb
index 99752ed396e..2fdbddd40c2 100644
--- a/spec/support/google_api/cloud_platform_helpers.rb
+++ b/spec/support/google_api/cloud_platform_helpers.rb
@@ -10,10 +10,14 @@ module GoogleApi
request.session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at] = 1.hour.ago.to_i.to_s
end
- def stub_google_project_billing_status
- redis_double = double
- allow(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis_double)
- allow(redis_double).to receive(:get).with(CheckGcpProjectBillingWorker.redis_shared_state_key_for('token')).and_return('true')
+ def stub_cloud_platform_projects_list(options)
+ WebMock.stub_request(:get, cloud_platform_projects_list_url)
+ .to_return(cloud_platform_response(cloud_platform_projects_body(options)))
+ end
+
+ def stub_cloud_platform_projects_get_billing_info(project_id, billing_enabled)
+ WebMock.stub_request(:get, cloud_platform_projects_get_billing_info_url(project_id))
+ .to_return(cloud_platform_response(cloud_platform_projects_billing_info_body(project_id, billing_enabled)))
end
def stub_cloud_platform_get_zone_cluster(project_id, zone, cluster_id, **options)
@@ -46,6 +50,14 @@ module GoogleApi
.to_return(status: [500, "Internal Server Error"])
end
+ def cloud_platform_projects_list_url
+ "https://cloudresourcemanager.googleapis.com/v1/projects"
+ end
+
+ def cloud_platform_projects_get_billing_info_url(project_id)
+ "https://cloudbilling.googleapis.com/v1/projects/#{project_id}/billingInfo"
+ end
+
def cloud_platform_get_zone_cluster_url(project_id, zone, cluster_id)
"https://container.googleapis.com/v1/projects/#{project_id}/zones/#{zone}/clusters/#{cluster_id}"
end
@@ -121,5 +133,32 @@ module GoogleApi
"endTime": options[:endTime] || ''
}
end
+
+ def cloud_platform_projects_body(**options)
+ {
+ "projects": [
+ {
+ "projectNumber": options[:project_number] || "1234",
+ "projectId": options[:project_id] || "test-project-1234",
+ "lifecycleState": "ACTIVE",
+ "name": options[:name] || "test-project",
+ "createTime": "2017-12-16T01:48:29.129Z",
+ "parent": {
+ "type": "organization",
+ "id": "12345"
+ }
+ }
+ ]
+ }
+ end
+
+ def cloud_platform_projects_billing_info_body(project_id, billing_enabled)
+ {
+ "name": "projects/#{project_id}/billingInfo",
+ "projectId": "#{project_id}",
+ "billingAccountName": "account-name",
+ "billingEnabled": billing_enabled
+ }
+ end
end
end
diff --git a/spec/tasks/gitlab/uploads_rake_spec.rb b/spec/tasks/gitlab/uploads_rake_spec.rb
new file mode 100644
index 00000000000..ac0005e51e0
--- /dev/null
+++ b/spec/tasks/gitlab/uploads_rake_spec.rb
@@ -0,0 +1,27 @@
+require 'rake_helper'
+
+describe 'gitlab:uploads rake tasks' do
+ describe 'check' do
+ let!(:upload) { create(:upload, path: Rails.root.join('spec/fixtures/banana_sample.gif')) }
+
+ before do
+ Rake.application.rake_require 'tasks/gitlab/uploads'
+ end
+
+ it 'outputs the integrity check for each uploaded file' do
+ expect { run_rake_task('gitlab:uploads:check') }.to output(/Checking file \(#{upload.id}\): #{Regexp.quote(upload.absolute_path)}/).to_stdout
+ end
+
+ it 'errors out about missing files on the file system' do
+ create(:upload)
+
+ expect { run_rake_task('gitlab:uploads:check') }.to output(/File does not exist on the file system/).to_stdout
+ end
+
+ it 'errors out about invalid checksum' do
+ upload.update_column(:checksum, '01a3156db2cf4f67ec823680b40b7302f89ab39179124ad219f94919b8a1769e')
+
+ expect { run_rake_task('gitlab:uploads:check') }.to output(/File checksum \(9e697aa09fe196909813ee36103e34f721fe47a5fdc8aac0e4e4ac47b9b38282\) does not match the one in the database \(#{upload.checksum}\)/).to_stdout
+ end
+ end
+end
diff --git a/spec/views/projects/buttons/_dropdown.html.haml_spec.rb b/spec/views/projects/buttons/_dropdown.html.haml_spec.rb
new file mode 100644
index 00000000000..d0e692635b9
--- /dev/null
+++ b/spec/views/projects/buttons/_dropdown.html.haml_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+describe 'projects/buttons/_dropdown' do
+ let(:user) { create(:user) }
+
+ context 'user with all abilities' do
+ before do
+ assign(:project, project)
+
+ allow(view).to receive(:current_user).and_return(user)
+ allow(view).to receive(:can?).and_return(true)
+ end
+
+ context 'empty repository' do
+ let(:project) { create(:project, :empty_repo) }
+
+ it 'has a link to create a new file' do
+ render
+
+ expect(view).to render_template('projects/buttons/_dropdown')
+ expect(rendered).to have_link('New file')
+ end
+
+ it 'does not have a link to create a new branch' do
+ render
+
+ expect(view).to render_template('projects/buttons/_dropdown')
+ expect(rendered).not_to have_link('New branch')
+ end
+
+ it 'does not have a link to create a new tag' do
+ render
+
+ expect(view).to render_template('projects/buttons/_dropdown')
+ expect(rendered).not_to have_link('New tag')
+ end
+ end
+ end
+end
diff --git a/spec/workers/check_gcp_project_billing_worker_spec.rb b/spec/workers/check_gcp_project_billing_worker_spec.rb
index f52a903327c..7b7a7c1bc44 100644
--- a/spec/workers/check_gcp_project_billing_worker_spec.rb
+++ b/spec/workers/check_gcp_project_billing_worker_spec.rb
@@ -8,7 +8,7 @@ describe CheckGcpProjectBillingWorker do
context 'when there is a token in redis' do
before do
- allow_any_instance_of(described_class).to receive(:get_session_token).and_return(token)
+ allow(described_class).to receive(:get_session_token).and_return(token)
end
context 'when there is no lease' do
diff --git a/yarn.lock b/yarn.lock
index da9e50739cd..5d40e833889 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -97,6 +97,10 @@ acorn@^5.0.0, acorn@^5.0.3, acorn@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.1.1.tgz#53fe161111f912ab999ee887a90a0bc52822fd75"
+acorn@^5.2.1:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.3.0.tgz#7446d39459c54fb49a80e6ee6478149b940ec822"
+
after@0.8.2:
version "0.8.2"
resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"
@@ -2069,14 +2073,14 @@ domelementtype@~1.1.1:
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"
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.1.tgz#892e47000a99be55bbf3774ffea0561d8879c259"
dependencies:
domelementtype "1"
domutils@^1.5.1:
- version "1.5.1"
- resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf"
+ version "1.6.2"
+ resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.6.2.tgz#1958cc0b4c9426e9ed367fb1c8e854891b0fa3ff"
dependencies:
dom-serializer "0"
domelementtype "1"
@@ -2368,7 +2372,7 @@ eslint-plugin-filenames@^1.1.0:
lodash.kebabcase "4.0.1"
lodash.snakecase "4.0.1"
-eslint-plugin-html@^2.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:
@@ -2397,7 +2401,25 @@ eslint-plugin-promise@^3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-3.5.0.tgz#78fbb6ffe047201627569e85a6c5373af2a68fca"
-eslint@^3.10.1:
+eslint-plugin-vue@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-4.0.1.tgz#afda92cfd7e7363b1fbdb1a772dd63359a9ce96a"
+ dependencies:
+ require-all "^2.2.0"
+ vue-eslint-parser "^2.0.1"
+
+eslint-scope@^3.7.1:
+ version "3.7.1"
+ resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8"
+ dependencies:
+ esrecurse "^4.1.0"
+ estraverse "^4.1.1"
+
+eslint-visitor-keys@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
+
+eslint@^3.18.0:
version "3.19.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-3.19.0.tgz#c8fc6201c7f40dd08941b87c085767386a679acc"
dependencies:
@@ -2444,6 +2466,13 @@ espree@^3.4.0:
acorn "^5.1.1"
acorn-jsx "^3.0.0"
+espree@^3.5.2:
+ version "3.5.2"
+ resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.2.tgz#756ada8b979e9dcfcdb30aad8d1a9304a905e1ca"
+ dependencies:
+ acorn "^5.2.1"
+ acorn-jsx "^3.0.0"
+
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"
@@ -5546,6 +5575,10 @@ request@^2.81.0:
tunnel-agent "^0.6.0"
uuid "^3.0.0"
+require-all@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/require-all/-/require-all-2.2.0.tgz#b4420c233ac0282d0ff49b277fb880a8b5de0894"
+
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
@@ -6504,6 +6537,17 @@ void-elements@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
+vue-eslint-parser@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-2.0.1.tgz#30135771c4fad00fdbac4542a2d59f3b1d776834"
+ dependencies:
+ debug "^3.1.0"
+ eslint-scope "^3.7.1"
+ eslint-visitor-keys "^1.0.0"
+ espree "^3.5.2"
+ esquery "^1.0.0"
+ lodash "^4.17.4"
+
vue-hot-reload-api@^2.2.0:
version "2.2.4"
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.2.4.tgz#683bd1d026c0d3b3c937d5875679e9a87ec6cd8f"