summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFilipa Lacerda <filipa@gitlab.com>2018-01-10 16:20:37 +0000
committerFilipa Lacerda <filipa@gitlab.com>2018-01-10 16:20:37 +0000
commit287309c3890f615d6eee4266873ab92822e29eac (patch)
tree34c707fb76d461f4c9a87e3154d59383780bd366
parentecee58154e8a4b706c10a7e718edb13306357f8e (diff)
parentdae93ef1ce2fbfe6342d6ec540f1b187d92a7cec (diff)
downloadgitlab-ce-41723-babel.tar.gz
Merge branch 'master' into 41723-babel41723-babel
* master: (67 commits) Remove Beta notice for Clusters Adds explanation to .codeclimate.yml file Disable eslint in codeclimate Fix broken modal Refactor dashboard milestone show inside dispatcher Refactor dashboard milestone index inside dispatcher Fix hooks not being set up properly for bare import Rake task Change order of reference notes in permissions docs Fix indention Fix indention Disable rule in modal.vue breaking tests Revert "Fix Route validation for unchanged path" Revert "Add test for permanent redirects of descendants" Add test for permanent redirects of descendants Fix Route validation for unchanged path Fix broken tests Migrate GitlabProject (re)move project endpoints Add support for multiple refmaps to RemoteService#add_remote Fixing bug related to wiki last version Restore sidebar collapse button padding ...
-rw-r--r--.codeclimate.yml3
-rw-r--r--.eslintrc22
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile6
-rw-r--r--Gemfile.lock4
-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.js30
-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.vue40
-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.vue90
-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/conversational_development_index/show/index.js3
-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/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/import/fogbugz/new_user_map/index.js3
-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.vue14
-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/filters.scss2
-rw-r--r--app/assets/stylesheets/framework/variables.scss3
-rw-r--r--app/assets/stylesheets/pages/diff.scss4
-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/helpers/application_settings_helper.rb1
-rw-r--r--app/helpers/diff_helper.rb8
-rw-r--r--app/models/application_setting.rb1
-rw-r--r--app/models/repository.rb4
-rw-r--r--app/views/admin/application_settings/_form.html.haml16
-rw-r--r--app/views/projects/diffs/_stats.html.haml2
-rw-r--r--changelogs/unreleased/changes-dropdown-ellipsis.yml5
-rw-r--r--changelogs/unreleased/fj-41477-fix-bug-wiki-last-version.yml5
-rw-r--r--changelogs/unreleased/fj-41681-add-param-disable-commit-stats-api.yml5
-rw-r--r--changelogs/unreleased/jej-backport-authorized-keys-to-ce.yml5
-rw-r--r--changelogs/unreleased/sh-fix-bare-import-hooks.yml5
-rw-r--r--config/initializers/gollum.rb20
-rw-r--r--db/migrate/20160301174731_add_fingerprint_index.rb17
-rw-r--r--db/migrate/20170531180233_add_authorized_keys_enabled_to_application_settings.rb19
-rw-r--r--db/schema.rb1
-rw-r--r--doc/administration/operations/fast_ssh_key_lookup.md170
-rw-r--r--doc/administration/operations/img/write_to_authorized_keys_setting.pngbin0 -> 94218 bytes
-rw-r--r--doc/administration/operations/index.md3
-rw-r--r--doc/administration/operations/speed_up_ssh.md1
-rw-r--r--doc/api/commits.md1
-rw-r--r--doc/api/repositories.md2
-rw-r--r--doc/ci/examples/code_climate.md3
-rw-r--r--doc/ci/ssh_keys/README.md2
-rw-r--r--doc/development/architecture.md2
-rw-r--r--doc/development/changelog.md2
-rw-r--r--doc/development/fe_guide/style_guide_js.md37
-rw-r--r--doc/user/permissions.md19
-rw-r--r--doc/user/project/clusters/index.md3
-rw-r--r--doc/user/project/integrations/irker.md6
-rw-r--r--doc/user/project/integrations/webhooks.md6
-rw-r--r--lib/api/commits.rb3
-rw-r--r--lib/api/entities.rb2
-rw-r--r--lib/api/internal.rb12
-rw-r--r--lib/api/v3/commits.rb3
-rw-r--r--lib/gitlab/bare_repository_import/importer.rb1
-rw-r--r--lib/gitlab/git/gitlab_projects.rb30
-rw-r--r--lib/gitlab/gitaly_client/remote_service.rb8
-rw-r--r--lib/gitlab/insecure_key_fingerprint.rb23
-rw-r--r--lib/gitlab/shell.rb92
-rw-r--r--package.json5
-rw-r--r--spec/helpers/diff_helper_spec.rb10
-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/pipelines/empty_state_spec.js4
-rw-r--r--spec/javascripts/registry/components/app_spec.js2
-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/bare_repository_import/importer_spec.rb8
-rw-r--r--spec/lib/gitlab/git/gitlab_projects_spec.rb45
-rw-r--r--spec/lib/gitlab/insecure_key_fingerprint_spec.rb18
-rw-r--r--spec/lib/gitlab/shell_spec.rb358
-rw-r--r--spec/models/repository_spec.rb22
-rw-r--r--spec/requests/api/commits_spec.rb25
-rw-r--r--spec/requests/api/internal_spec.rb48
-rw-r--r--spec/requests/api/v3/commits_spec.rb27
-rw-r--r--spec/workers/gitlab_shell_worker_spec.rb12
-rw-r--r--yarn.lock56
263 files changed, 6967 insertions, 5340 deletions
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/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/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/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 9e8b2acfe1b..a282b67b0fc 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -13,7 +13,6 @@ 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';
@@ -190,15 +189,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();
@@ -216,6 +225,10 @@ 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':
@@ -539,22 +552,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':
case 'admin:impersonation_tokens:index':
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..400306759b2 100644
--- a/app/assets/javascripts/groups/components/app.vue
+++ b/app/assets/javascripts/groups/components/app.vue
@@ -42,6 +42,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('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);
+ },
methods: {
fetchGroups({ parentId, page, filterGroupsBy, sortBy, archived, updatePagination }) {
return this.service.getGroups(parentId, page, filterGroupsBy, sortBy, archived)
@@ -152,26 +172,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>
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..1bde6ae5185 100644
--- a/app/assets/javascripts/groups/components/item_actions.vue
+++ b/app/assets/javascripts/groups/components/item_actions.vue
@@ -1,56 +1,56 @@
<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';
+ 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,
- },
- props: {
- parentGroup: {
- type: Object,
- required: false,
- default: () => ({}),
+ export default {
+ components: {
+ icon,
+ modal,
},
- group: {
- type: Object,
- required: true,
+ directives: {
+ tooltip,
},
- },
- data() {
- return {
- modalStatus: false,
- };
- },
- computed: {
- leaveBtnTitle() {
- return COMMON_STR.LEAVE_BTN_TITLE;
+ props: {
+ parentGroup: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ group: {
+ type: Object,
+ required: true,
+ },
},
- editBtnTitle() {
- return COMMON_STR.EDIT_BTN_TITLE;
+ data() {
+ return {
+ modalStatus: false,
+ };
},
- leaveConfirmationMessage() {
- return s__(`GroupsTree|Are you sure you want to leave the "${this.group.fullName}" group?`);
+ computed: {
+ leaveBtnTitle() {
+ return COMMON_STR.LEAVE_BTN_TITLE;
+ },
+ 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;
+ methods: {
+ onLeaveGroup() {
+ this.modalStatus = true;
+ },
+ leaveGroup() {
+ this.modalStatus = false;
+ eventHub.$emit('leaveGroup', this.group, this.parentGroup);
+ },
},
- leaveGroup() {
- this.modalStatus = false;
- eventHub.$emit('leaveGroup', this.group, this.parentGroup);
- },
- },
-};
+ };
</script>
<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/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/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/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/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/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..e18852af6e9 100644
--- a/app/assets/javascripts/vue_shared/components/clipboard_button.vue
+++ b/app/assets/javascripts/vue_shared/components/clipboard_button.vue
@@ -4,7 +4,7 @@
*/
export default {
- name: 'clipboardButton',
+ name: 'ClipboardButton',
props: {
text: {
type: String,
@@ -23,10 +23,12 @@
type="button"
class="btn btn-transparent btn-clipboard"
:data-title="title"
- :data-clipboard-text="text">
- <i
- aria-hidden="true"
- class="fa fa-clipboard">
- </i>
+ :data-clipboard-text="text"
+ >
+ <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/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 60b07537799..1d081b58f62 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -651,15 +651,13 @@
min-width: 0;
}
- .diff-changed-file-name,
- .diff-changed-file-path {
+ .diff-changed-file-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.diff-changed-file-path {
- direction: rtl;
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/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index b12ea760668..45f7d29eb05 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -146,6 +146,7 @@ module ApplicationSettingsHelper
:after_sign_up_text,
:akismet_api_key,
:akismet_enabled,
+ :authorized_keys_enabled,
:auto_devops_enabled,
:circuitbreaker_access_retries,
:circuitbreaker_check_interval,
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index 1ce487e6592..0f5fc2823a3 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -226,4 +226,12 @@ module DiffHelper
diffs.overflow?
end
+
+ def diff_file_path_text(diff_file, max: 60)
+ path = diff_file.new_path
+
+ return path unless path.size > max && max > 3
+
+ "...#{path[-(max - 3)..-1]}"
+ end
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 253e213af81..8ab338d873d 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -261,6 +261,7 @@ class ApplicationSetting < ActiveRecord::Base
{
after_sign_up_text: nil,
akismet_enabled: false,
+ authorized_keys_enabled: true, # TODO default to false if the instance is configured to use AuthorizedKeysCommand
container_registry_token_expire_delay: 5,
default_artifacts_expire_in: '30 days',
default_branch_protection: Settings.gitlab['default_branch_protection'],
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 9c879e2006b..b36e756c07c 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)
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 3e2dbb07a6c..ba4ca88a8a9 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -775,6 +775,22 @@
= link_to icon('question-circle'), help_page_path('administration/polling')
%fieldset
+ %legend Performance optimization
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :authorized_keys_enabled do
+ = f.check_box :authorized_keys_enabled
+ Write to "authorized_keys" file
+ .help-block
+ By default, we write to the "authorized_keys" file to support Git
+ over SSH without additional configuration. GitLab can be optimized
+ to authenticate SSH keys via the database file. Only uncheck this
+ if you have configured your OpenSSH server to use the
+ AuthorizedKeysCommand. Click on the help icon for more details.
+ = link_to icon('question-circle'), help_page_path('administration/operations/fast_ssh_key_lookup')
+
+ %fieldset
%legend User and IP Rate Limits
.form-group
.col-sm-offset-2.col-sm-10
diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml
index dd473ebe580..325159dd9a7 100644
--- a/app/views/projects/diffs/_stats.html.haml
+++ b/app/views/projects/diffs/_stats.html.haml
@@ -25,7 +25,7 @@
= 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
- %span.diff-changed-file-path.prepend-top-5= diff_file.new_path
+ %span.diff-changed-file-path.prepend-top-5= diff_file_path_text(diff_file)
%span.diff-changed-stats
%span.cgreen<
+#{diff_file.added_lines}
diff --git a/changelogs/unreleased/changes-dropdown-ellipsis.yml b/changelogs/unreleased/changes-dropdown-ellipsis.yml
new file mode 100644
index 00000000000..7e3f378cc33
--- /dev/null
+++ b/changelogs/unreleased/changes-dropdown-ellipsis.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed chanages dropdown ellipsis positioning
+merge_request:
+author:
+type: fixed
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/fj-41681-add-param-disable-commit-stats-api.yml b/changelogs/unreleased/fj-41681-add-param-disable-commit-stats-api.yml
new file mode 100644
index 00000000000..dca4dec224c
--- /dev/null
+++ b/changelogs/unreleased/fj-41681-add-param-disable-commit-stats-api.yml
@@ -0,0 +1,5 @@
+---
+title: Added option to disable commits stats in the commit endpoint
+merge_request: 16309
+author:
+type: added
diff --git a/changelogs/unreleased/jej-backport-authorized-keys-to-ce.yml b/changelogs/unreleased/jej-backport-authorized-keys-to-ce.yml
new file mode 100644
index 00000000000..4386c631f59
--- /dev/null
+++ b/changelogs/unreleased/jej-backport-authorized-keys-to-ce.yml
@@ -0,0 +1,5 @@
+---
+title: Backport fast database lookup of SSH authorized_keys from EE
+merge_request: 16014
+author:
+type: added
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/20160301174731_add_fingerprint_index.rb b/db/migrate/20160301174731_add_fingerprint_index.rb
new file mode 100644
index 00000000000..f2c3d1ba1ea
--- /dev/null
+++ b/db/migrate/20160301174731_add_fingerprint_index.rb
@@ -0,0 +1,17 @@
+# rubocop:disable all
+class AddFingerprintIndex < ActiveRecord::Migration
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+
+ # https://gitlab.com/gitlab-org/gitlab-ee/issues/764
+ def change
+ args = [:keys, :fingerprint]
+
+ if Gitlab::Database.postgresql?
+ args << { algorithm: :concurrently }
+ end
+
+ add_index(*args) unless index_exists?(:keys, :fingerprint)
+ end
+end
diff --git a/db/migrate/20170531180233_add_authorized_keys_enabled_to_application_settings.rb b/db/migrate/20170531180233_add_authorized_keys_enabled_to_application_settings.rb
new file mode 100644
index 00000000000..1d86a531eb3
--- /dev/null
+++ b/db/migrate/20170531180233_add_authorized_keys_enabled_to_application_settings.rb
@@ -0,0 +1,19 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddAuthorizedKeysEnabledToApplicationSettings < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default :application_settings, :authorized_keys_enabled, :boolean, default: true, allow_null: false
+ end
+
+ def down
+ remove_column :application_settings, :authorized_keys_enabled
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index e6a2ea4c862..a16f756ccfb 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -154,6 +154,7 @@ ActiveRecord::Schema.define(version: 20171230123729) do
t.integer "gitaly_timeout_default", default: 55, null: false
t.integer "gitaly_timeout_medium", default: 30, null: false
t.integer "gitaly_timeout_fast", default: 10, null: false
+ t.boolean "authorized_keys_enabled", default: true, null: false
end
create_table "audit_events", force: :cascade do |t|
diff --git a/doc/administration/operations/fast_ssh_key_lookup.md b/doc/administration/operations/fast_ssh_key_lookup.md
new file mode 100644
index 00000000000..835ed8c8006
--- /dev/null
+++ b/doc/administration/operations/fast_ssh_key_lookup.md
@@ -0,0 +1,170 @@
+# Fast lookup of authorized SSH keys in the database
+
+Regular SSH operations become slow as the number of users grows because OpenSSH
+searches for a key to authorize a user via a linear search. In the worst case,
+such as when the user is not authorized to access GitLab, OpenSSH will scan the
+entire file to search for a key. This can take significant time and disk I/O,
+which will delay users attempting to push or pull to a repository. Making
+matters worse, if users add or remove keys frequently, the operating system may
+not be able to cache the `authorized_keys` file, which causes the disk to be
+accessed repeatedly.
+
+GitLab Shell solves this by providing a way to authorize SSH users via a fast,
+indexed lookup in the GitLab database. This page describes how to enable the fast
+lookup of authorized SSH keys.
+
+> **Warning:** OpenSSH version 6.9+ is required because
+`AuthorizedKeysCommand` must be able to accept a fingerprint. These
+instructions will break installations using older versions of OpenSSH, such as
+those included with CentOS 6 as of September 2017. If you want to use this
+feature for CentOS 6, follow [the instructions on how to build and install a custom OpenSSH package](#compiling-a-custom-version-of-openssh-for-centos-6) before continuing.
+
+## Setting up fast lookup via GitLab Shell
+
+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.
+
+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/embedded/gitlab-shell/bin/gitlab-shell-authorized-keys-check git %u %k
+AuthorizedKeysCommandUser git
+```
+
+Reload OpenSSH:
+
+```bash
+# Debian or Ubuntu installations
+sudo service ssh reload
+
+# CentOS installations
+sudo service sshd reload
+```
+
+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.
+
+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
+users as long as a large file exists.
+
+You can disable any more writes to the `authorized_keys` file by unchecking
+`Write to "authorized_keys" file` in the Application Settings of your GitLab
+installation.
+
+![Write to authorized keys setting](img/write_to_authorized_keys_setting.png)
+
+Again, 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.
+
+Then you can backup and delete your `authorized_keys` file for best performance.
+
+## How to go back to using the `authorized_keys` file
+
+This is a brief overview. Please refer to the above instructions for more context.
+
+1. [Rebuild the `authorized_keys` file](../raketasks/maintenance.md#rebuild-authorized_keys-file)
+1. Enable writes to the `authorized_keys` file in Application Settings
+1. Remove the `AuthorizedKeysCommand` lines from `/etc/ssh/sshd_config` or from `/assets/sshd_config` if you are using Omnibus Docker.
+1. Reload sshd: `sudo service sshd reload`
+1. Remove the `/opt/gitlab-shell/authorized_keys` file
+
+## Compiling a custom version of OpenSSH for CentOS 6
+
+Building a custom version of OpenSSH is not necessary for Ubuntu 16.04 users,
+since Ubuntu 16.04 ships with OpenSSH 7.2.
+
+It is also unnecessary for CentOS 7.4 users, as that version ships with
+OpenSSH 7.4. If you are using CentOS 7.0 - 7.3, we strongly recommend that you
+upgrade to CentOS 7.4 instead of following this procedure. This should be as
+simple as running `yum update`.
+
+CentOS 6 users must build their own OpenSSH package to enable SSH lookups via
+the database. The following instructions can be used to build OpenSSH 7.5:
+
+1. First, download the package and install the required packages:
+
+ ```
+ sudo su -
+ cd /tmp
+ curl --remote-name https://mirrors.evowise.com/pub/OpenBSD/OpenSSH/portable/openssh-7.5p1.tar.gz
+ tar xzvf openssh-7.5p1.tar.gz
+ yum install rpm-build gcc make wget openssl-devel krb5-devel pam-devel libX11-devel xmkmf libXt-devel
+ ```
+
+3. Prepare the build by copying files to the right place:
+
+ ```
+ mkdir -p /root/rpmbuild/{SOURCES,SPECS}
+ cp ./openssh-7.5p1/contrib/redhat/openssh.spec /root/rpmbuild/SPECS/
+ cp openssh-7.5p1.tar.gz /root/rpmbuild/SOURCES/
+ cd /root/rpmbuild/SPECS
+ ```
+
+3. Next, set the spec settings properly:
+
+ ```
+ sed -i -e "s/%define no_gnome_askpass 0/%define no_gnome_askpass 1/g" openssh.spec
+ sed -i -e "s/%define no_x11_askpass 0/%define no_x11_askpass 1/g" openssh.spec
+ sed -i -e "s/BuildPreReq/BuildRequires/g" openssh.spec
+ ```
+
+3. Build the RPMs:
+
+ ```
+ rpmbuild -bb openssh.spec
+ ```
+
+4. Ensure the RPMs were built:
+
+ ```
+ ls -al /root/rpmbuild/RPMS/x86_64/
+ ```
+
+ You should see something as the following:
+
+ ```
+ total 1324
+ drwxr-xr-x. 2 root root 4096 Jun 20 19:37 .
+ drwxr-xr-x. 3 root root 19 Jun 20 19:37 ..
+ -rw-r--r--. 1 root root 470828 Jun 20 19:37 openssh-7.5p1-1.x86_64.rpm
+ -rw-r--r--. 1 root root 490716 Jun 20 19:37 openssh-clients-7.5p1-1.x86_64.rpm
+ -rw-r--r--. 1 root root 17020 Jun 20 19:37 openssh-debuginfo-7.5p1-1.x86_64.rpm
+ -rw-r--r--. 1 root root 367516 Jun 20 19:37 openssh-server-7.5p1-1.x86_64.rpm
+ ```
+
+5. Install the packages. OpenSSH packages will replace `/etc/pam.d/sshd`
+ with its own version, which may prevent users from logging in, so be sure
+ that the file is backed up and restored after installation:
+
+ ```
+ timestamp=$(date +%s)
+ cp /etc/pam.d/sshd pam-ssh-conf-$timestamp
+ rpm -Uvh /root/rpmbuild/RPMS/x86_64/*.rpm
+ yes | cp pam-ssh-conf-$timestamp /etc/pam.d/sshd
+ ```
+
+6. Verify the installed version. In another window, attempt to login to the server:
+
+ ```
+ ssh -v <your-centos-machine>
+ ```
+
+ You should see a line that reads: "debug1: Remote protocol version 2.0, remote software version OpenSSH_7.5"
+
+ If not, you may need to restart sshd (e.g. `systemctl restart sshd.service`).
+
+7. *IMPORTANT!* Open a new SSH session to your server before exiting to make
+ sure everything is working! If you need to downgrade, simple install the
+ older package:
+
+ ```
+ # Only run this if you run into a problem logging in
+ yum downgrade openssh-server openssh openssh-clients
+ ```
diff --git a/doc/administration/operations/img/write_to_authorized_keys_setting.png b/doc/administration/operations/img/write_to_authorized_keys_setting.png
new file mode 100644
index 00000000000..232765f1917
--- /dev/null
+++ b/doc/administration/operations/img/write_to_authorized_keys_setting.png
Binary files differ
diff --git a/doc/administration/operations/index.md b/doc/administration/operations/index.md
index 320d71a9527..5655b7efec6 100644
--- a/doc/administration/operations/index.md
+++ b/doc/administration/operations/index.md
@@ -13,4 +13,5 @@ by GitLab to another file system or another server.
that to prioritize important jobs.
- [Sidekiq MemoryKiller](sidekiq_memory_killer.md): Configure Sidekiq MemoryKiller
to restart Sidekiq.
-- [Unicorn](unicorn.md): Understand Unicorn and unicorn-worker-killer. \ No newline at end of file
+- [Unicorn](unicorn.md): Understand Unicorn and unicorn-worker-killer.
+- [Speed up SSH operations](fast_ssh_key_lookup.md): Authorize SSH users via a fast, indexed lookup to the GitLab database.
diff --git a/doc/administration/operations/speed_up_ssh.md b/doc/administration/operations/speed_up_ssh.md
new file mode 100644
index 00000000000..89265b3018b
--- /dev/null
+++ b/doc/administration/operations/speed_up_ssh.md
@@ -0,0 +1 @@
+This document was moved to [another location](fast_ssh_key_lookup.md).
diff --git a/doc/api/commits.md b/doc/api/commits.md
index c9b72d4a1dd..63554c63057 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -159,6 +159,7 @@ Parameters:
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
| `sha` | string | yes | The commit hash or name of a repository branch or tag |
+| `stats` | boolean | no | Include commit stats. Default is true |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/repository/commits/master
diff --git a/doc/api/repositories.md b/doc/api/repositories.md
index 03b32577872..5fb25e40ed7 100644
--- a/doc/api/repositories.md
+++ b/doc/api/repositories.md
@@ -113,7 +113,7 @@ GET /projects/:id/repository/archive
Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
-- `sha` (optional) - The commit SHA to download defaults to the tip of the default branch
+- `sha` (optional) - The commit SHA to download. A tag, branch reference or sha can be used. This defaults to the tip of the default branch if not specified
## Compare branches, tags or commits
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/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md
index df0e1521150..b8df0bfba20 100644
--- a/doc/ci/ssh_keys/README.md
+++ b/doc/ci/ssh_keys/README.md
@@ -181,7 +181,7 @@ before_script:
## Assuming you created the SSH_KNOWN_HOSTS variable, uncomment the
## following two lines.
##
- - echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts'
+ - echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts
##
diff --git a/doc/development/architecture.md b/doc/development/architecture.md
index 54029e00507..d1ba7d3dfc3 100644
--- a/doc/development/architecture.md
+++ b/doc/development/architecture.md
@@ -133,8 +133,6 @@ Usage: /etc/init.d/postgresql {start|stop|restart|reload|force-reload|status} [v
### Log locations of the services
-Note: `/home/git/` is shorthand for `/home/git`.
-
gitlabhq (includes Unicorn and Sidekiq logs)
- `/home/git/gitlab/log/` contains `application.log`, `production.log`, `sidekiq.log`, `unicorn.stdout.log`, `githost.log` and `unicorn.stderr.log` normally.
diff --git a/doc/development/changelog.md b/doc/development/changelog.md
index 48cffc0dd18..18f4177a5e5 100644
--- a/doc/development/changelog.md
+++ b/doc/development/changelog.md
@@ -127,7 +127,7 @@ type:
If you're working on the GitLab EE repository, the entry will be added to
`changelogs/unreleased-ee/` instead.
-#### Arguments
+### Arguments
| Argument | Shorthand | Purpose |
| ----------------- | --------- | ---------------------------------------------------------------------------------------------------------- |
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/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..5f14d232cb1 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.
diff --git a/doc/user/project/integrations/irker.md b/doc/user/project/integrations/irker.md
index c63ea1316fe..ecdd83ce8f0 100644
--- a/doc/user/project/integrations/irker.md
+++ b/doc/user/project/integrations/irker.md
@@ -47,4 +47,8 @@ Irker accepts channel names of the form `chan` and `#chan`, both for the
case, `Aorimn` is treated as a nick and no more as a channel name.
Irker can also join password-protected channels. Users need to append
-`?key=thesecretpassword` to the chan name.
+`?key=thesecretpassword` to the chan name. When using this feature remember to
+**not** put the `#` sign in front of the channel name; failing to do so will
+result on irker joining a channel literally named `#chan?key=password` henceforth
+leaking the channel key through the `/whois` IRC command (depending on IRC server
+configuration). This is due to a long standing irker bug.
diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md
index eafdd28071d..82175c70e49 100644
--- a/doc/user/project/integrations/webhooks.md
+++ b/doc/user/project/integrations/webhooks.md
@@ -54,6 +54,12 @@ Below are described the supported events.
Triggered when you push to the repository except when pushing tags.
+> **Note:** When more than 20 commits are pushed at once, the `commits` web hook
+ attribute will only contain the first 20 for performance reasons. Loading
+ detailed commit data is expensive. Note that despite only 20 commits being
+ present in the `commits` attribute, the `total_commits_count` attribute will
+ contain the actual total.
+
**Request header**:
```
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 38e05074353..d8fd6a6eb06 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -82,13 +82,14 @@ module API
end
params do
requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag'
+ optional :stats, type: Boolean, default: true, desc: 'Include commit stats'
end
get ':id/repository/commits/:sha', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
commit = user_project.commit(params[:sha])
not_found! 'Commit' unless commit
- present commit, with: Entities::CommitDetail
+ present commit, with: Entities::CommitDetail, stats: params[:stats]
end
desc 'Get the diff for a specific commit of a project' do
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index bd0c54a1b04..f574858be02 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -278,7 +278,7 @@ module API
end
class CommitDetail < Commit
- expose :stats, using: Entities::CommitStats
+ expose :stats, using: Entities::CommitStats, if: :stats
expose :status
expose :last_pipeline, using: 'API::Entities::PipelineBasic'
end
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 79b302aae70..8bf53939751 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -82,6 +82,18 @@ module API
end
#
+ # Get a ssh key using the fingerprint
+ #
+ get "/authorized_keys" do
+ fingerprint = params.fetch(:fingerprint) do
+ Gitlab::InsecureKeyFingerprint.new(params.fetch(:key)).fingerprint
+ end
+ key = Key.find_by(fingerprint: fingerprint)
+ not_found!("Key") if key.nil?
+ present key, with: Entities::SSHKey
+ end
+
+ #
# Discover user by ssh key or user id
#
get "/discover" do
diff --git a/lib/api/v3/commits.rb b/lib/api/v3/commits.rb
index 0ef26aa696a..4f6ea8f502e 100644
--- a/lib/api/v3/commits.rb
+++ b/lib/api/v3/commits.rb
@@ -71,13 +71,14 @@ module API
end
params do
requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag'
+ optional :stats, type: Boolean, default: true, desc: 'Include commit stats'
end
get ":id/repository/commits/:sha", requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
commit = user_project.commit(params[:sha])
not_found! "Commit" unless commit
- present commit, with: ::API::Entities::CommitDetail
+ present commit, with: ::API::Entities::CommitDetail, stats: params[:stats]
end
desc 'Get the diff for a specific commit of a project' do
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/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/insecure_key_fingerprint.rb b/lib/gitlab/insecure_key_fingerprint.rb
new file mode 100644
index 00000000000..f85b6e9197f
--- /dev/null
+++ b/lib/gitlab/insecure_key_fingerprint.rb
@@ -0,0 +1,23 @@
+module Gitlab
+ #
+ # Calculates the fingerprint of a given key without using
+ # openssh key validations. For this reason, only use
+ # for calculating the fingerprint to find the key with it.
+ #
+ # DO NOT use it for checking the validity of a ssh key.
+ #
+ class InsecureKeyFingerprint
+ attr_accessor :key
+
+ #
+ # Gets the base64 encoded string representing a rsa or dsa key
+ #
+ def initialize(key_base64)
+ @key = key_base64
+ end
+
+ def fingerprint
+ OpenSSL::Digest::MD5.hexdigest(Base64.decode64(@key)).scan(/../).join(':')
+ end
+ end
+end
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index a8a4ec996c4..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
@@ -183,6 +195,8 @@ module Gitlab
# add_key("key-42", "sha-rsa ...")
#
def add_key(key_id, key_content)
+ return unless self.authorized_keys_enabled?
+
gitlab_shell_fast_execute([gitlab_shell_keys_path,
'add-key', key_id, self.class.strip_key(key_content)])
end
@@ -192,6 +206,8 @@ module Gitlab
# Ex.
# batch_add_keys { |adder| adder.add_key("key-42", "sha-rsa ...") }
def batch_add_keys(&block)
+ return unless self.authorized_keys_enabled?
+
IO.popen(%W(#{gitlab_shell_path}/bin/gitlab-keys batch-add-keys), 'w') do |io|
yield(KeyAdder.new(io))
end
@@ -202,10 +218,11 @@ module Gitlab
# Ex.
# remove_key("key-342", "sha-rsa ...")
#
- def remove_key(key_id, key_content)
+ def remove_key(key_id, key_content = nil)
+ return unless self.authorized_keys_enabled?
+
args = [gitlab_shell_keys_path, 'rm-key', key_id]
args << key_content if key_content
-
gitlab_shell_fast_execute(args)
end
@@ -215,9 +232,62 @@ module Gitlab
# remove_all_keys
#
def remove_all_keys
+ return unless self.authorized_keys_enabled?
+
gitlab_shell_fast_execute([gitlab_shell_keys_path, 'clear'])
end
+ # Remove ssh keys from gitlab shell that are not in the DB
+ #
+ # Ex.
+ # remove_keys_not_found_in_db
+ #
+ def remove_keys_not_found_in_db
+ return unless self.authorized_keys_enabled?
+
+ Rails.logger.info("Removing keys not found in DB")
+
+ batch_read_key_ids do |ids_in_file|
+ ids_in_file.uniq!
+ keys_in_db = Key.where(id: ids_in_file)
+
+ next unless ids_in_file.size > keys_in_db.count # optimization
+
+ ids_to_remove = ids_in_file - keys_in_db.pluck(:id)
+ ids_to_remove.each do |id|
+ Rails.logger.info("Removing key-#{id} not found in DB")
+ remove_key("key-#{id}")
+ end
+ end
+ end
+
+ # Iterate over all ssh key IDs from gitlab shell, in batches
+ #
+ # Ex.
+ # batch_read_key_ids { |batch| keys = Key.where(id: batch) }
+ #
+ def batch_read_key_ids(batch_size: 100, &block)
+ return unless self.authorized_keys_enabled?
+
+ list_key_ids do |key_id_stream|
+ key_id_stream.lazy.each_slice(batch_size) do |lines|
+ key_ids = lines.map { |l| l.chomp.to_i }
+ yield(key_ids)
+ end
+ end
+ end
+
+ # Stream all ssh key IDs from gitlab shell, separated by newlines
+ #
+ # Ex.
+ # list_key_ids
+ #
+ def list_key_ids(&block)
+ return unless self.authorized_keys_enabled?
+
+ IO.popen(%W(#{gitlab_shell_path}/bin/gitlab-keys list-key-ids), &block)
+ end
+
# Add empty directory for storing repositories
#
# Ex.
@@ -255,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
#
@@ -274,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"
@@ -333,6 +405,14 @@ module Gitlab
File.join(gitlab_shell_path, 'bin', 'gitlab-keys')
end
+ def authorized_keys_enabled?
+ # Return true if nil to ensure the authorized_keys methods work while
+ # fixing the authorized_keys file during migration.
+ return true if Gitlab::CurrentSettings.current_application_settings.authorized_keys_enabled.nil?
+
+ Gitlab::CurrentSettings.current_application_settings.authorized_keys_enabled
+ end
+
private
def gitlab_projects(shard_path, disk_path)
diff --git a/package.json b/package.json
index b3aa2478fbc..fa5e60c8f8b 100644
--- a/package.json
+++ b/package.json
@@ -38,7 +38,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 @@
"babel-plugin-transform-define": "^1.3.0",
"babel-preset-latest": "^6.24.1",
"babel-preset-stage-2": "^6.24.1",
- "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/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb
index f9c31ac61d8..15cbe36ae76 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -266,4 +266,14 @@ describe DiffHelper do
end
end
end
+
+ context '#diff_file_path_text' do
+ it 'returns full path by default' do
+ expect(diff_file_path_text(diff_file)).to eq(diff_file.new_path)
+ end
+
+ it 'returns truncated path' do
+ expect(diff_file_path_text(diff_file, max: 10)).to eq("...open.rb")
+ end
+ end
end
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/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/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/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/insecure_key_fingerprint_spec.rb b/spec/lib/gitlab/insecure_key_fingerprint_spec.rb
new file mode 100644
index 00000000000..6532579b1c9
--- /dev/null
+++ b/spec/lib/gitlab/insecure_key_fingerprint_spec.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+
+describe Gitlab::InsecureKeyFingerprint do
+ let(:key) do
+ 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn' \
+ '1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qk' \
+ 'r8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMg' \
+ 'Jw0='
+ end
+
+ let(:fingerprint) { "3f:a2:ee:de:b5:de:53:c3:aa:2f:9c:45:24:4c:47:7b" }
+
+ describe "#fingerprint" do
+ it "generates the key's fingerprint" do
+ expect(described_class.new(key.split[1]).fingerprint).to eq(fingerprint)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index ffd2d2c7afc..2b61ce38418 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -52,6 +52,311 @@ describe Gitlab::Shell do
end
end
+ describe '#add_key' do
+ context 'when authorized_keys_enabled is true' do
+ it 'removes trailing garbage' do
+ allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
+ expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
+ [:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar']
+ )
+
+ gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
+ end
+ end
+
+ context 'when authorized_keys_enabled is false' do
+ before do
+ stub_application_setting(authorized_keys_enabled: false)
+ end
+
+ it 'does nothing' do
+ expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute)
+
+ gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
+ end
+ end
+
+ context 'when authorized_keys_enabled is nil' do
+ before do
+ stub_application_setting(authorized_keys_enabled: nil)
+ end
+
+ it 'removes trailing garbage' do
+ allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
+ expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
+ [:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar']
+ )
+
+ gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
+ end
+ end
+ end
+
+ describe '#batch_add_keys' do
+ context 'when authorized_keys_enabled is true' do
+ it 'instantiates KeyAdder' do
+ expect_any_instance_of(Gitlab::Shell::KeyAdder).to receive(:add_key).with('key-123', 'ssh-rsa foobar')
+
+ gitlab_shell.batch_add_keys do |adder|
+ adder.add_key('key-123', 'ssh-rsa foobar')
+ end
+ end
+ end
+
+ context 'when authorized_keys_enabled is false' do
+ before do
+ stub_application_setting(authorized_keys_enabled: false)
+ end
+
+ it 'does nothing' do
+ expect_any_instance_of(Gitlab::Shell::KeyAdder).not_to receive(:add_key)
+
+ gitlab_shell.batch_add_keys do |adder|
+ adder.add_key('key-123', 'ssh-rsa foobar')
+ end
+ end
+ end
+
+ context 'when authorized_keys_enabled is nil' do
+ before do
+ stub_application_setting(authorized_keys_enabled: nil)
+ end
+
+ it 'instantiates KeyAdder' do
+ expect_any_instance_of(Gitlab::Shell::KeyAdder).to receive(:add_key).with('key-123', 'ssh-rsa foobar')
+
+ gitlab_shell.batch_add_keys do |adder|
+ adder.add_key('key-123', 'ssh-rsa foobar')
+ end
+ end
+ end
+ end
+
+ describe '#remove_key' do
+ context 'when authorized_keys_enabled is true' do
+ it 'removes trailing garbage' do
+ allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
+ expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
+ [:gitlab_shell_keys_path, 'rm-key', 'key-123', 'ssh-rsa foobar']
+ )
+
+ gitlab_shell.remove_key('key-123', 'ssh-rsa foobar')
+ end
+ end
+
+ context 'when authorized_keys_enabled is false' do
+ before do
+ stub_application_setting(authorized_keys_enabled: false)
+ end
+
+ it 'does nothing' do
+ expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute)
+
+ gitlab_shell.remove_key('key-123', 'ssh-rsa foobar')
+ end
+ end
+
+ context 'when authorized_keys_enabled is nil' do
+ before do
+ stub_application_setting(authorized_keys_enabled: nil)
+ end
+
+ it 'removes trailing garbage' do
+ allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
+ expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
+ [:gitlab_shell_keys_path, 'rm-key', 'key-123', 'ssh-rsa foobar']
+ )
+
+ gitlab_shell.remove_key('key-123', 'ssh-rsa foobar')
+ end
+ end
+
+ context 'when key content is not given' do
+ it 'calls rm-key with only one argument' do
+ allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
+ expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
+ [:gitlab_shell_keys_path, 'rm-key', 'key-123']
+ )
+
+ gitlab_shell.remove_key('key-123')
+ end
+ end
+ end
+
+ describe '#remove_all_keys' do
+ context 'when authorized_keys_enabled is true' do
+ it 'removes trailing garbage' do
+ allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
+ expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with([:gitlab_shell_keys_path, 'clear'])
+
+ gitlab_shell.remove_all_keys
+ end
+ end
+
+ context 'when authorized_keys_enabled is false' do
+ before do
+ stub_application_setting(authorized_keys_enabled: false)
+ end
+
+ it 'does nothing' do
+ expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute)
+
+ gitlab_shell.remove_all_keys
+ end
+ end
+
+ context 'when authorized_keys_enabled is nil' do
+ before do
+ stub_application_setting(authorized_keys_enabled: nil)
+ end
+
+ it 'removes trailing garbage' do
+ allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
+ expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
+ [:gitlab_shell_keys_path, 'clear']
+ )
+
+ gitlab_shell.remove_all_keys
+ end
+ end
+ end
+
+ describe '#remove_keys_not_found_in_db' do
+ context 'when keys are in the file that are not in the DB' do
+ before do
+ gitlab_shell.remove_all_keys
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
+ gitlab_shell.add_key('key-9876', 'ssh-rsa ASDFASDF')
+ @another_key = create(:key) # this one IS in the DB
+ end
+
+ it 'removes the keys' do
+ expect(find_in_authorized_keys_file(1234)).to be_truthy
+ expect(find_in_authorized_keys_file(9876)).to be_truthy
+ expect(find_in_authorized_keys_file(@another_key.id)).to be_truthy
+ gitlab_shell.remove_keys_not_found_in_db
+ expect(find_in_authorized_keys_file(1234)).to be_falsey
+ expect(find_in_authorized_keys_file(9876)).to be_falsey
+ expect(find_in_authorized_keys_file(@another_key.id)).to be_truthy
+ end
+ end
+
+ context 'when keys there are duplicate keys in the file that are not in the DB' do
+ before do
+ gitlab_shell.remove_all_keys
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
+ end
+
+ it 'removes the keys' do
+ expect(find_in_authorized_keys_file(1234)).to be_truthy
+ gitlab_shell.remove_keys_not_found_in_db
+ expect(find_in_authorized_keys_file(1234)).to be_falsey
+ end
+
+ it 'does not run remove more than once per key (in a batch)' do
+ expect(gitlab_shell).to receive(:remove_key).with('key-1234').once
+ gitlab_shell.remove_keys_not_found_in_db
+ end
+ end
+
+ context 'when keys there are duplicate keys in the file that ARE in the DB' do
+ before do
+ gitlab_shell.remove_all_keys
+ @key = create(:key)
+ gitlab_shell.add_key(@key.shell_id, @key.key)
+ end
+
+ it 'does not remove the key' do
+ gitlab_shell.remove_keys_not_found_in_db
+ expect(find_in_authorized_keys_file(@key.id)).to be_truthy
+ end
+
+ it 'does not need to run a SELECT query for that batch, on account of that key' do
+ expect_any_instance_of(ActiveRecord::Relation).not_to receive(:pluck)
+ gitlab_shell.remove_keys_not_found_in_db
+ end
+ end
+
+ unless ENV['CI'] # Skip in CI, it takes 1 minute
+ context 'when the first batch can be skipped, but the next batch has keys that are not in the DB' do
+ before do
+ gitlab_shell.remove_all_keys
+ 100.times { |i| create(:key) } # first batch is all in the DB
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
+ end
+
+ it 'removes the keys not in the DB' do
+ expect(find_in_authorized_keys_file(1234)).to be_truthy
+ gitlab_shell.remove_keys_not_found_in_db
+ expect(find_in_authorized_keys_file(1234)).to be_falsey
+ end
+ end
+ end
+ end
+
+ describe '#batch_read_key_ids' do
+ context 'when there are keys in the authorized_keys file' do
+ before do
+ gitlab_shell.remove_all_keys
+ (1..4).each do |i|
+ gitlab_shell.add_key("key-#{i}", "ssh-rsa ASDFASDF#{i}")
+ end
+ end
+
+ it 'iterates over the key IDs in the file, in batches' do
+ loop_count = 0
+ first_batch = [1, 2]
+ second_batch = [3, 4]
+
+ gitlab_shell.batch_read_key_ids(batch_size: 2) do |batch|
+ expected = (loop_count == 0 ? first_batch : second_batch)
+ expect(batch).to eq(expected)
+ loop_count += 1
+ end
+ end
+ end
+ end
+
+ describe '#list_key_ids' do
+ context 'when there are keys in the authorized_keys file' do
+ before do
+ gitlab_shell.remove_all_keys
+ (1..4).each do |i|
+ gitlab_shell.add_key("key-#{i}", "ssh-rsa ASDFASDF#{i}")
+ end
+ end
+
+ it 'outputs the key IDs in the file, separated by newlines' do
+ ids = []
+ gitlab_shell.list_key_ids do |io|
+ io.each do |line|
+ ids << line
+ end
+ end
+
+ expect(ids).to eq(%W{1\n 2\n 3\n 4\n})
+ end
+ end
+
+ context 'when there are no keys in the authorized_keys file' do
+ before do
+ gitlab_shell.remove_all_keys
+ end
+
+ it 'outputs nothing, not even an empty string' do
+ ids = []
+ gitlab_shell.list_key_ids do |io|
+ io.each do |line|
+ ids << line
+ end
+ end
+
+ expect(ids).to eq([])
+ end
+ end
+ end
+
describe Gitlab::Shell::KeyAdder do
describe '#add_key' do
it 'removes trailing garbage' do
@@ -97,17 +402,6 @@ describe Gitlab::Shell do
allow(Gitlab.config.gitlab_shell).to receive(:git_timeout).and_return(800)
end
- describe '#add_key' do
- it 'removes trailing garbage' do
- allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
- expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
- [:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar']
- )
-
- gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
- end
- end
-
describe '#add_repository' do
shared_examples '#add_repository' do
let(:repository_storage) { 'default' }
@@ -149,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(project2.repository_storage_path, old_path, new_path)).to be_truthy
- expect(gitlab_shell.mv_repository(project.repository_storage_path, project.disk_path, 'project/newpath')).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
@@ -412,4 +718,12 @@ describe Gitlab::Shell do
end
end
end
+
+ def find_in_authorized_keys_file(key_id)
+ gitlab_shell.batch_read_key_ids do |ids|
+ return true if ids.include?(key_id)
+ end
+
+ false
+ end
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/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 0d2bd3207c0..34db50dc082 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -512,6 +512,31 @@ describe API::Commits do
end
end
+ context 'when stat param' do
+ let(:route) { "/projects/#{project_id}/repository/commits/#{commit_id}" }
+
+ it 'is not present return stats by default' do
+ get api(route, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to include 'stats'
+ end
+
+ it "is false it does not include stats" do
+ get api(route, user), stats: false
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).not_to include 'stats'
+ end
+
+ it "is true it includes stats" do
+ get api(route, user), stats: true
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to include 'stats'
+ end
+ end
+
context 'when unauthenticated', 'and project is public' do
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index 7b25047ea8f..2783c51b8df 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -192,6 +192,54 @@ describe API::Internal do
end
end
+ describe "GET /internal/authorized_keys" do
+ context "using an existing key's fingerprint" do
+ it "finds the key" do
+ get(api('/internal/authorized_keys'), fingerprint: key.fingerprint, secret_token: secret_token)
+
+ expect(response.status).to eq(200)
+ expect(json_response["key"]).to eq(key.key)
+ end
+ end
+
+ context "non existing key's fingerprint" do
+ it "returns 404" do
+ get(api('/internal/authorized_keys'), fingerprint: "no:t-:va:li:d0", secret_token: secret_token)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context "using a partial fingerprint" do
+ it "returns 404" do
+ get(api('/internal/authorized_keys'), fingerprint: "#{key.fingerprint[0..5]}%", secret_token: secret_token)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context "sending the key" do
+ it "finds the key" do
+ get(api('/internal/authorized_keys'), key: key.key.split[1], secret_token: secret_token)
+
+ expect(response.status).to eq(200)
+ expect(json_response["key"]).to eq(key.key)
+ end
+
+ it "returns 404 with a partial key" do
+ get(api('/internal/authorized_keys'), key: key.key.split[1][0...-3], secret_token: secret_token)
+
+ expect(response.status).to eq(404)
+ end
+
+ it "returns 404 with an not valid base64 string" do
+ get(api('/internal/authorized_keys'), key: "whatever!", secret_token: secret_token)
+
+ expect(response.status).to eq(404)
+ end
+ end
+ end
+
describe "POST /internal/allowed", :clean_gitlab_redis_shared_state do
context "access granted" do
around do |example|
diff --git a/spec/requests/api/v3/commits_spec.rb b/spec/requests/api/v3/commits_spec.rb
index 8b115e01f47..34c543bffe8 100644
--- a/spec/requests/api/v3/commits_spec.rb
+++ b/spec/requests/api/v3/commits_spec.rb
@@ -403,6 +403,33 @@ describe API::V3::Commits do
expect(response).to have_gitlab_http_status(200)
expect(json_response['status']).to eq("created")
end
+
+ context 'when stat param' do
+ let(:project_id) { project.id }
+ let(:commit_id) { project.repository.commit.id }
+ let(:route) { "/projects/#{project_id}/repository/commits/#{commit_id}" }
+
+ it 'is not present return stats by default' do
+ get v3_api(route, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to include 'stats'
+ end
+
+ it "is false it does not include stats" do
+ get v3_api(route, user), stats: false
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).not_to include 'stats'
+ end
+
+ it "is true it includes stats" do
+ get v3_api(route, user), stats: true
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to include 'stats'
+ end
+ end
end
context "unauthorized user" do
diff --git a/spec/workers/gitlab_shell_worker_spec.rb b/spec/workers/gitlab_shell_worker_spec.rb
new file mode 100644
index 00000000000..6b222af454d
--- /dev/null
+++ b/spec/workers/gitlab_shell_worker_spec.rb
@@ -0,0 +1,12 @@
+require 'spec_helper'
+
+describe GitlabShellWorker do
+ let(:worker) { described_class.new }
+
+ describe '#perform with add_key' do
+ it 'calls add_key on Gitlab::Shell' do
+ expect_any_instance_of(Gitlab::Shell).to receive(:add_key).with('foo', 'bar')
+ worker.perform(:add_key, 'foo', 'bar')
+ end
+ end
+end
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"