summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClement Ho <clemmakesapps@gmail.com>2018-01-10 16:44:14 +0000
committerClement Ho <clemmakesapps@gmail.com>2018-01-10 16:44:14 +0000
commit827761c1658ccc62a7883aba36dc35dd9cc3c5a1 (patch)
tree453582440bf9eeb8202c2390f8478e7c4adcf671
parent2bd4453ca2c4bec527a264194cf12d164cd31ed7 (diff)
parent82007530cb08ccd8aff68aafdc506595f62a40a6 (diff)
downloadgitlab-ce-827761c1658ccc62a7883aba36dc35dd9cc3c5a1.tar.gz
Merge branch 'master' into 'explore-dispatcher-refactor'
# Conflicts: # app/assets/javascripts/dispatcher.js
-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/behaviors/index.js1
-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/boards/components/board_list.js2
-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.js32
-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/main.js1
-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/pipelines/pipelines_charts.js12
-rw-r--r--app/assets/javascripts/preview_markdown.js364
-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.scss10
-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/_file_header.html.haml3
-rw-r--r--app/views/projects/diffs/_stats.html.haml2
-rw-r--r--app/views/projects/pipelines/charts/_pipelines.haml4
-rw-r--r--changelogs/unreleased/36906-reordering-issues-to-the-bottom.yml5
-rw-r--r--changelogs/unreleased/changes-dropdown-ellipsis.yml5
-rw-r--r--changelogs/unreleased/da-verify-integrity-of-uploaded-files.yml5
-rw-r--r--changelogs/unreleased/fj-41477-fix-bug-wiki-last-version.yml5
-rw-r--r--changelogs/unreleased/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--changelogs/unreleased/sh-store-user-in-api-logs.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/administration/raketasks/check.md33
-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/api.rb3
-rw-r--r--lib/api/commits.rb3
-rw-r--r--lib/api/entities.rb2
-rw-r--r--lib/api/helpers.rb7
-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/grape_logging/loggers/user_logger.rb18
-rw-r--r--lib/gitlab/insecure_key_fingerprint.rb23
-rw-r--r--lib/gitlab/regex.rb2
-rw-r--r--lib/gitlab/shell.rb92
-rw-r--r--lib/tasks/gitlab/uploads.rake44
-rw-r--r--package.json7
-rw-r--r--spec/helpers/diff_helper_spec.rb10
-rw-r--r--spec/initializers/gollum_spec.rb62
-rw-r--r--spec/javascripts/boards/board_list_spec.js12
-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.js3
-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/regex_spec.rb3
-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/helpers_spec.rb6
-rw-r--r--spec/requests/api/internal_spec.rb48
-rw-r--r--spec/requests/api/v3/commits_spec.rb27
-rw-r--r--spec/tasks/gitlab/uploads_rake_spec.rb27
-rw-r--r--spec/workers/gitlab_shell_worker_spec.rb12
-rw-r--r--yarn.lock56
283 files changed, 7340 insertions, 5538 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/behaviors/index.js b/app/assets/javascripts/behaviors/index.js
index 34e905222b4..8d021de7998 100644
--- a/app/assets/javascripts/behaviors/index.js
+++ b/app/assets/javascripts/behaviors/index.js
@@ -7,6 +7,7 @@ import installGlEmojiElement from './gl_emoji';
import './quick_submit';
import './requires_input';
import './toggler_behavior';
+import '../preview_markdown';
installGlEmojiElement();
initCopyAsGFM();
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/boards/components/board_list.js b/app/assets/javascripts/boards/components/board_list.js
index 84b76a6f1b1..d8cf532fe78 100644
--- a/app/assets/javascripts/boards/components/board_list.js
+++ b/app/assets/javascripts/boards/components/board_list.js
@@ -187,7 +187,7 @@ export default {
<li
class="board-list-count text-center"
v-if="showCount"
- data-id="-1">
+ data-issue-id="-1">
<loading-icon
v-show="list.loadingMore"
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 11ec667fc07..08f12ba4d95 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';
@@ -188,15 +187,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();
@@ -212,6 +221,12 @@ import Activities from './activities';
case 'dashboard:todos:index':
import('./pages/dashboard/todos/index').then(callDefault).catch(fail);
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':
@@ -535,22 +550,19 @@ import Activities from './activities';
break;
case 'ci:lints:create':
case 'ci:lints:show':
- new CILintEditor();
+ import('./pages/ci/lints').then(m => m.default()).catch(fail);
break;
case 'users:show':
import('./pages/users/show').then(callDefault).catch(fail);
break;
case 'admin:conversational_development_index:show':
- new UserCallout();
+ import('./pages/admin/conversational_development_index/show').then(m => m.default()).catch(fail);
break;
case 'snippets:show':
- new LineHighlighter();
- new BlobViewer();
- initNotes();
- new ZenMode();
+ import('./pages/snippets/show').then(m => m.default()).catch(fail);
break;
case 'import:fogbugz:new_user_map':
- new UsersSelect();
+ import('./pages/import/fogbugz/new_user_map').then(m => m.default()).catch(fail);
break;
case 'profiles:personal_access_tokens:index':
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/main.js b/app/assets/javascripts/main.js
index 59bfa482bb0..ce6f91439b4 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -46,7 +46,6 @@ import LazyLoader from './lazy_loader';
import './line_highlighter';
import initLogoAnimation from './logo';
import './milestone_select';
-import './preview_markdown';
import './projects_dropdown';
import './render_gfm';
import initBreadcrumbs from './breadcrumb';
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/pipelines/pipelines_charts.js b/app/assets/javascripts/pipelines/pipelines_charts.js
index 001faf4be33..821aa7e229f 100644
--- a/app/assets/javascripts/pipelines/pipelines_charts.js
+++ b/app/assets/javascripts/pipelines/pipelines_charts.js
@@ -6,16 +6,16 @@ document.addEventListener('DOMContentLoaded', () => {
const data = {
labels: chartScope.labels,
datasets: [{
- fillColor: '#7f8fa4',
- strokeColor: '#7f8fa4',
- pointColor: '#7f8fa4',
+ fillColor: '#707070',
+ strokeColor: '#707070',
+ pointColor: '#707070',
pointStrokeColor: '#EEE',
data: chartScope.totalValues,
},
{
- fillColor: '#44aa22',
- strokeColor: '#44aa22',
- pointColor: '#44aa22',
+ fillColor: '#1aaa55',
+ strokeColor: '#1aaa55',
+ pointColor: '#1aaa55',
pointStrokeColor: '#fff',
data: chartScope.successValues,
},
diff --git a/app/assets/javascripts/preview_markdown.js b/app/assets/javascripts/preview_markdown.js
index ffaafb3ee9e..86c7b56198d 100644
--- a/app/assets/javascripts/preview_markdown.js
+++ b/app/assets/javascripts/preview_markdown.js
@@ -6,195 +6,193 @@
// (including the explanation of quick actions), and showing a warning when
// more than `x` users are referenced.
//
-(function () {
- var lastTextareaPreviewed;
- var lastTextareaHeight = null;
- var markdownPreview;
- var previewButtonSelector;
- var writeButtonSelector;
-
- window.MarkdownPreview = (function () {
- function MarkdownPreview() {}
-
- // Minimum number of users referenced before triggering a warning
- MarkdownPreview.prototype.referenceThreshold = 10;
- MarkdownPreview.prototype.emptyMessage = 'Nothing to preview.';
-
- MarkdownPreview.prototype.ajaxCache = {};
-
- MarkdownPreview.prototype.showPreview = function ($form) {
- var mdText;
- var preview = $form.find('.js-md-preview');
- var url = preview.data('url');
- if (preview.hasClass('md-preview-loading')) {
- return;
- }
- mdText = $form.find('textarea.markdown-area').val();
-
- if (mdText.trim().length === 0) {
- preview.text(this.emptyMessage);
- this.hideReferencedUsers($form);
- } else {
- preview.addClass('md-preview-loading').text('Loading...');
- this.fetchMarkdownPreview(mdText, url, (function (response) {
- var body;
- if (response.body.length > 0) {
- body = response.body;
- } else {
- body = this.emptyMessage;
- }
-
- preview.removeClass('md-preview-loading').html(body);
- preview.renderGFM();
- this.renderReferencedUsers(response.references.users, $form);
-
- if (response.references.commands) {
- this.renderReferencedCommands(response.references.commands, $form);
- }
- }).bind(this));
- }
- };
- MarkdownPreview.prototype.fetchMarkdownPreview = function (text, url, success) {
- if (!url) {
- return;
- }
- if (text === this.ajaxCache.text) {
- success(this.ajaxCache.response);
- return;
- }
- $.ajax({
- type: 'POST',
- url: url,
- data: {
- text: text
- },
- dataType: 'json',
- success: (function (response) {
- this.ajaxCache = {
- text: text,
- response: response
- };
- success(response);
- }).bind(this)
- });
- };
-
- MarkdownPreview.prototype.hideReferencedUsers = function ($form) {
- $form.find('.referenced-users').hide();
- };
-
- MarkdownPreview.prototype.renderReferencedUsers = function (users, $form) {
- var referencedUsers;
- referencedUsers = $form.find('.referenced-users');
- if (referencedUsers.length) {
- if (users.length >= this.referenceThreshold) {
- referencedUsers.show();
- referencedUsers.find('.js-referenced-users-count').text(users.length);
- } else {
- referencedUsers.hide();
- }
- }
- };
-
- MarkdownPreview.prototype.hideReferencedCommands = function ($form) {
- $form.find('.referenced-commands').hide();
- };
-
- MarkdownPreview.prototype.renderReferencedCommands = function (commands, $form) {
- var referencedCommands;
- referencedCommands = $form.find('.referenced-commands');
- if (commands.length > 0) {
- referencedCommands.html(commands);
- referencedCommands.show();
+var lastTextareaPreviewed;
+var lastTextareaHeight = null;
+var markdownPreview;
+var previewButtonSelector;
+var writeButtonSelector;
+
+function MarkdownPreview() {}
+
+// Minimum number of users referenced before triggering a warning
+MarkdownPreview.prototype.referenceThreshold = 10;
+MarkdownPreview.prototype.emptyMessage = 'Nothing to preview.';
+
+MarkdownPreview.prototype.ajaxCache = {};
+
+MarkdownPreview.prototype.showPreview = function ($form) {
+ var mdText;
+ var preview = $form.find('.js-md-preview');
+ var url = preview.data('url');
+ if (preview.hasClass('md-preview-loading')) {
+ return;
+ }
+ mdText = $form.find('textarea.markdown-area').val();
+
+ if (mdText.trim().length === 0) {
+ preview.text(this.emptyMessage);
+ this.hideReferencedUsers($form);
+ } else {
+ preview.addClass('md-preview-loading').text('Loading...');
+ this.fetchMarkdownPreview(mdText, url, (function (response) {
+ var body;
+ if (response.body.length > 0) {
+ body = response.body;
} else {
- referencedCommands.html('');
- referencedCommands.hide();
+ body = this.emptyMessage;
}
- };
-
- return MarkdownPreview;
- }());
-
- markdownPreview = new window.MarkdownPreview();
- previewButtonSelector = '.js-md-preview-button';
- writeButtonSelector = '.js-md-write-button';
- lastTextareaPreviewed = null;
- const markdownToolbar = $('.md-header-toolbar');
-
- $.fn.setupMarkdownPreview = function () {
- var $form = $(this);
- $form.find('textarea.markdown-area').on('input', function () {
- markdownPreview.hideReferencedUsers($form);
- });
- };
-
- $(document).on('markdown-preview:show', function (e, $form) {
- if (!$form) {
- return;
- }
-
- lastTextareaPreviewed = $form.find('textarea.markdown-area');
- lastTextareaHeight = lastTextareaPreviewed.height();
-
- // toggle tabs
- $form.find(writeButtonSelector).parent().removeClass('active');
- $form.find(previewButtonSelector).parent().addClass('active');
- // toggle content
- $form.find('.md-write-holder').hide();
- $form.find('.md-preview-holder').show();
- markdownToolbar.removeClass('active');
- markdownPreview.showPreview($form);
- });
-
- $(document).on('markdown-preview:hide', function (e, $form) {
- if (!$form) {
- return;
- }
- lastTextareaPreviewed = null;
-
- if (lastTextareaHeight) {
- $form.find('textarea.markdown-area').height(lastTextareaHeight);
- }
-
- // toggle tabs
- $form.find(writeButtonSelector).parent().addClass('active');
- $form.find(previewButtonSelector).parent().removeClass('active');
-
- // toggle content
- $form.find('.md-write-holder').show();
- $form.find('textarea.markdown-area').focus();
- $form.find('.md-preview-holder').hide();
- markdownToolbar.addClass('active');
+ preview.removeClass('md-preview-loading').html(body);
+ preview.renderGFM();
+ this.renderReferencedUsers(response.references.users, $form);
- markdownPreview.hideReferencedCommands($form);
+ if (response.references.commands) {
+ this.renderReferencedCommands(response.references.commands, $form);
+ }
+ }).bind(this));
+ }
+};
+
+MarkdownPreview.prototype.fetchMarkdownPreview = function (text, url, success) {
+ if (!url) {
+ return;
+ }
+ if (text === this.ajaxCache.text) {
+ success(this.ajaxCache.response);
+ return;
+ }
+ $.ajax({
+ type: 'POST',
+ url: url,
+ data: {
+ text: text
+ },
+ dataType: 'json',
+ success: (function (response) {
+ this.ajaxCache = {
+ text: text,
+ response: response
+ };
+ success(response);
+ }).bind(this)
});
-
- $(document).on('markdown-preview:toggle', function (e, keyboardEvent) {
- var $target;
- $target = $(keyboardEvent.target);
- if ($target.is('textarea.markdown-area')) {
- $(document).triggerHandler('markdown-preview:show', [$target.closest('form')]);
- keyboardEvent.preventDefault();
- } else if (lastTextareaPreviewed) {
- $target = lastTextareaPreviewed;
- $(document).triggerHandler('markdown-preview:hide', [$target.closest('form')]);
- keyboardEvent.preventDefault();
+};
+
+MarkdownPreview.prototype.hideReferencedUsers = function ($form) {
+ $form.find('.referenced-users').hide();
+};
+
+MarkdownPreview.prototype.renderReferencedUsers = function (users, $form) {
+ var referencedUsers;
+ referencedUsers = $form.find('.referenced-users');
+ if (referencedUsers.length) {
+ if (users.length >= this.referenceThreshold) {
+ referencedUsers.show();
+ referencedUsers.find('.js-referenced-users-count').text(users.length);
+ } else {
+ referencedUsers.hide();
}
+ }
+};
+
+MarkdownPreview.prototype.hideReferencedCommands = function ($form) {
+ $form.find('.referenced-commands').hide();
+};
+
+MarkdownPreview.prototype.renderReferencedCommands = function (commands, $form) {
+ var referencedCommands;
+ referencedCommands = $form.find('.referenced-commands');
+ if (commands.length > 0) {
+ referencedCommands.html(commands);
+ referencedCommands.show();
+ } else {
+ referencedCommands.html('');
+ referencedCommands.hide();
+ }
+};
+
+markdownPreview = new MarkdownPreview();
+
+previewButtonSelector = '.js-md-preview-button';
+writeButtonSelector = '.js-md-write-button';
+lastTextareaPreviewed = null;
+const markdownToolbar = $('.md-header-toolbar');
+
+$.fn.setupMarkdownPreview = function () {
+ var $form = $(this);
+ $form.find('textarea.markdown-area').on('input', function () {
+ markdownPreview.hideReferencedUsers($form);
});
+};
+
+$(document).on('markdown-preview:show', function (e, $form) {
+ if (!$form) {
+ return;
+ }
+
+ lastTextareaPreviewed = $form.find('textarea.markdown-area');
+ lastTextareaHeight = lastTextareaPreviewed.height();
+
+ // toggle tabs
+ $form.find(writeButtonSelector).parent().removeClass('active');
+ $form.find(previewButtonSelector).parent().addClass('active');
+
+ // toggle content
+ $form.find('.md-write-holder').hide();
+ $form.find('.md-preview-holder').show();
+ markdownToolbar.removeClass('active');
+ markdownPreview.showPreview($form);
+});
+
+$(document).on('markdown-preview:hide', function (e, $form) {
+ if (!$form) {
+ return;
+ }
+ lastTextareaPreviewed = null;
- $(document).on('click', previewButtonSelector, function (e) {
- var $form;
- e.preventDefault();
- $form = $(this).closest('form');
- $(document).triggerHandler('markdown-preview:show', [$form]);
- });
-
- $(document).on('click', writeButtonSelector, function (e) {
- var $form;
- e.preventDefault();
- $form = $(this).closest('form');
- $(document).triggerHandler('markdown-preview:hide', [$form]);
- });
-}());
+ if (lastTextareaHeight) {
+ $form.find('textarea.markdown-area').height(lastTextareaHeight);
+ }
+
+ // toggle tabs
+ $form.find(writeButtonSelector).parent().addClass('active');
+ $form.find(previewButtonSelector).parent().removeClass('active');
+
+ // toggle content
+ $form.find('.md-write-holder').show();
+ $form.find('textarea.markdown-area').focus();
+ $form.find('.md-preview-holder').hide();
+ markdownToolbar.addClass('active');
+
+ markdownPreview.hideReferencedCommands($form);
+});
+
+$(document).on('markdown-preview:toggle', function (e, keyboardEvent) {
+ var $target;
+ $target = $(keyboardEvent.target);
+ if ($target.is('textarea.markdown-area')) {
+ $(document).triggerHandler('markdown-preview:show', [$target.closest('form')]);
+ keyboardEvent.preventDefault();
+ } else if (lastTextareaPreviewed) {
+ $target = lastTextareaPreviewed;
+ $(document).triggerHandler('markdown-preview:hide', [$target.closest('form')]);
+ keyboardEvent.preventDefault();
+ }
+});
+
+$(document).on('click', previewButtonSelector, function (e) {
+ var $form;
+ e.preventDefault();
+ $form = $(this).closest('form');
+ $(document).triggerHandler('markdown-preview:show', [$form]);
+});
+
+$(document).on('click', writeButtonSelector, function (e) {
+ var $form;
+ e.preventDefault();
+ $form = $(this).closest('form');
+ $(document).triggerHandler('markdown-preview:hide', [$form]);
+});
+
+export default MarkdownPreview;
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 05c1033c5f7..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;
}
@@ -993,3 +993,11 @@ button.mini-pipeline-graph-dropdown-toggle {
font-weight: $gl-font-weight-normal;
line-height: 1.5;
}
+
+.legend-all {
+ color: $gl-text-color-secondary;
+}
+
+.legend-success {
+ color: $green-500;
+}
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/_file_header.html.haml b/app/views/projects/diffs/_file_header.html.haml
index 73c316472e3..dbeddf6689a 100644
--- a/app/views/projects/diffs/_file_header.html.haml
+++ b/app/views/projects/diffs/_file_header.html.haml
@@ -35,3 +35,6 @@
- if diff_file.mode_changed?
%small
#{diff_file.a_mode} → #{diff_file.b_mode}
+
+ - if diff_file.stored_externally? && diff_file.external_storage == :lfs
+ %span.label.label-lfs.append-right-5 LFS
diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml
index 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/app/views/projects/pipelines/charts/_pipelines.haml b/app/views/projects/pipelines/charts/_pipelines.haml
index 7a100843f5e..41dc2f6cf9d 100644
--- a/app/views/projects/pipelines/charts/_pipelines.haml
+++ b/app/views/projects/pipelines/charts/_pipelines.haml
@@ -4,11 +4,11 @@
%h4= _("Pipelines charts")
%p
&nbsp;
- %span.cgreen
+ %span.legend-success
= icon("circle")
= s_("Pipeline|success")
&nbsp;
- %span.cgray
+ %span.legend-all
= icon("circle")
= s_("Pipeline|all")
diff --git a/changelogs/unreleased/36906-reordering-issues-to-the-bottom.yml b/changelogs/unreleased/36906-reordering-issues-to-the-bottom.yml
new file mode 100644
index 00000000000..0ab765a43b7
--- /dev/null
+++ b/changelogs/unreleased/36906-reordering-issues-to-the-bottom.yml
@@ -0,0 +1,5 @@
+---
+title: "Issue board: fix for dragging an issue to the very bottom in long lists"
+merge_request: 16250
+author: David Kuri
+type: fixed \ No newline at end of file
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/da-verify-integrity-of-uploaded-files.yml b/changelogs/unreleased/da-verify-integrity-of-uploaded-files.yml
new file mode 100644
index 00000000000..5b850c92d17
--- /dev/null
+++ b/changelogs/unreleased/da-verify-integrity-of-uploaded-files.yml
@@ -0,0 +1,5 @@
+---
+title: Add rake task to check integrity of uploaded files
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/fj-41477-fix-bug-wiki-last-version.yml b/changelogs/unreleased/fj-41477-fix-bug-wiki-last-version.yml
new file mode 100644
index 00000000000..e4b1343876a
--- /dev/null
+++ b/changelogs/unreleased/fj-41477-fix-bug-wiki-last-version.yml
@@ -0,0 +1,5 @@
+---
+title: Fixing bug when wiki last version
+merge_request: 16197
+author:
+type: fixed
diff --git a/changelogs/unreleased/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/changelogs/unreleased/sh-store-user-in-api-logs.yml b/changelogs/unreleased/sh-store-user-in-api-logs.yml
new file mode 100644
index 00000000000..d904dcaf6d3
--- /dev/null
+++ b/changelogs/unreleased/sh-store-user-in-api-logs.yml
@@ -0,0 +1,5 @@
+---
+title: Save user ID and username in Grape API log (api_json.log)
+merge_request:
+author:
+type: changed
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/administration/raketasks/check.md b/doc/administration/raketasks/check.md
index c39cb49b1c6..d1ed152b58c 100644
--- a/doc/administration/raketasks/check.md
+++ b/doc/administration/raketasks/check.md
@@ -76,6 +76,39 @@ Example output:
![gitlab:user:check_repos output](../img/raketasks/check_repos_output.png)
+## Uploaded Files Integrity
+
+The uploads check Rake task will loop through all uploads in the database
+and run two checks to determine the integrity of each file:
+
+1. Check if the file exist on the file system.
+1. Check if the checksum of the file on the file system matches the checksum in the database.
+
+**Omnibus Installation**
+
+```
+sudo gitlab-rake gitlab:uploads:check
+```
+
+**Source Installation**
+
+```bash
+sudo -u git -H bundle exec rake gitlab:uploads:check RAILS_ENV=production
+```
+
+This task also accepts some environment variables which you can use to override
+certain values:
+
+Variable | Type | Description
+-------- | ---- | -----------
+`BATCH` | integer | Specifies the size of the batch. Defaults to 200.
+`ID_FROM` | integer | Specifies the ID to start from, inclusive of the value.
+`ID_TO` | integer | Specifies the ID value to end at, inclusive of the value.
+
+```bash
+sudo gitlab-rake gitlab:uploads:check BATCH=100 ID_FROM=50 ID_TO=250
+```
+
## LDAP Check
The LDAP check Rake task will test the bind_dn and password credentials
diff --git a/doc/api/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/api.rb b/lib/api/api.rb
index e0d14281c96..ae161efb358 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -13,7 +13,8 @@ module API
formatter: Gitlab::GrapeLogging::Formatters::LogrageWithTimestamp.new,
include: [
GrapeLogging::Loggers::FilterParameters.new,
- GrapeLogging::Loggers::ClientEnv.new
+ GrapeLogging::Loggers::ClientEnv.new,
+ Gitlab::GrapeLogging::Loggers::UserLogger.new
]
allow_access_with_scope :api
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/helpers.rb b/lib/api/helpers.rb
index bf388163ec8..d6ce368efd5 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -5,6 +5,7 @@ module API
SUDO_HEADER = "HTTP_SUDO".freeze
SUDO_PARAM = :sudo
+ API_USER_ENV = 'gitlab.api.user'.freeze
def declared_params(options = {})
options = { include_parent_namespaces: false }.merge(options)
@@ -48,10 +49,16 @@ module API
validate_access_token!(scopes: scopes_registered_for_endpoint) unless sudo?
+ save_current_user_in_env(@current_user) if @current_user
+
@current_user
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
+ def save_current_user_in_env(user)
+ env[API_USER_ENV] = { user_id: user.id, username: user.username }
+ end
+
def sudo?
initial_current_user != current_user
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/grape_logging/loggers/user_logger.rb b/lib/gitlab/grape_logging/loggers/user_logger.rb
new file mode 100644
index 00000000000..fa172861967
--- /dev/null
+++ b/lib/gitlab/grape_logging/loggers/user_logger.rb
@@ -0,0 +1,18 @@
+# This grape_logging module (https://github.com/aserafin/grape_logging) makes it
+# possible to log the user who performed the Grape API action by retrieving
+# the user context from the request environment.
+module Gitlab
+ module GrapeLogging
+ module Loggers
+ class UserLogger < ::GrapeLogging::Loggers::Base
+ def parameters(request, _)
+ params = request.env[::API::Helpers::API_USER_ENV]
+
+ return {} unless params
+
+ params.slice(:user_id, :username)
+ end
+ end
+ end
+ end
+end
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/regex.rb b/lib/gitlab/regex.rb
index 2c7b8af83f2..0002c7da8f1 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -37,7 +37,7 @@ module Gitlab
end
def environment_name_regex_chars
- 'a-zA-Z0-9_/\\$\\{\\}\\. -'
+ 'a-zA-Z0-9_/\\$\\{\\}\\. \\-'
end
def environment_name_regex
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/lib/tasks/gitlab/uploads.rake b/lib/tasks/gitlab/uploads.rake
new file mode 100644
index 00000000000..df31567ce64
--- /dev/null
+++ b/lib/tasks/gitlab/uploads.rake
@@ -0,0 +1,44 @@
+namespace :gitlab do
+ namespace :uploads do
+ desc 'GitLab | Uploads | Check integrity of uploaded files'
+ task check: :environment do
+ puts 'Checking integrity of uploaded files'
+
+ uploads_batches do |batch|
+ batch.each do |upload|
+ puts "- Checking file (#{upload.id}): #{upload.absolute_path}".color(:green)
+
+ if upload.exist?
+ check_checksum(upload)
+ else
+ puts " * File does not exist on the file system".color(:red)
+ end
+ end
+ end
+
+ puts 'Done!'
+ end
+
+ def batch_size
+ ENV.fetch('BATCH', 200).to_i
+ end
+
+ def calculate_checksum(absolute_path)
+ Digest::SHA256.file(absolute_path).hexdigest
+ end
+
+ def check_checksum(upload)
+ checksum = calculate_checksum(upload.absolute_path)
+
+ if checksum != upload.checksum
+ puts " * File checksum (#{checksum}) does not match the one in the database (#{upload.checksum})".color(:red)
+ end
+ end
+
+ def uploads_batches(&block)
+ Upload.all.in_batches(of: batch_size, start: ENV['ID_FROM'], finish: ENV['ID_TO']) do |relation| # rubocop: disable Cop/InBatches
+ yield relation
+ end
+ end
+ end
+end
diff --git a/package.json b/package.json
index 1f83f62e7f7..4759ae76817 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,6 @@
"dependencies": {
"autosize": "^4.0.0",
"axios": "^0.17.1",
- "axios-mock-adapter": "^1.10.0",
"babel-core": "^6.26.0",
"babel-eslint": "^8.0.2",
"babel-loader": "^7.1.2",
@@ -45,7 +44,6 @@
"document-register-element": "1.3.0",
"dropzone": "^4.2.0",
"emoji-unicode-version": "^0.2.1",
- "eslint-plugin-html": "^2.0.1",
"exports-loader": "^0.6.4",
"file-loader": "^0.11.1",
"fuzzaldrin-plus": "^0.5.0",
@@ -89,14 +87,17 @@
},
"devDependencies": {
"@gitlab-org/gitlab-svgs": "^1.5.0",
+ "axios-mock-adapter": "^1.10.0",
"babel-plugin-istanbul": "^4.1.5",
- "eslint": "^3.10.1",
+ "eslint": "^3.18.0",
"eslint-config-airbnb-base": "^10.0.1",
"eslint-import-resolver-webpack": "^0.8.3",
"eslint-plugin-filenames": "^1.1.0",
+ "eslint-plugin-html": "2.0.1",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-jasmine": "^2.1.0",
"eslint-plugin-promise": "^3.5.0",
+ "eslint-plugin-vue": "^4.0.1",
"istanbul": "^0.4.5",
"jasmine-core": "^2.6.3",
"jasmine-jquery": "^2.1.1",
diff --git a/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/board_list_spec.js b/spec/javascripts/boards/board_list_spec.js
index 7c5888b6d82..b7cc3a8813e 100644
--- a/spec/javascripts/boards/board_list_spec.js
+++ b/spec/javascripts/boards/board_list_spec.js
@@ -154,6 +154,18 @@ describe('Board list component', () => {
});
});
+ it('sets data attribute with invalid id', (done) => {
+ component.showCount = true;
+
+ Vue.nextTick(() => {
+ expect(
+ component.$el.querySelector('.board-list-count').getAttribute('data-issue-id'),
+ ).toBe('-1');
+
+ done();
+ });
+ });
+
it('shows how many more issues to load', (done) => {
component.showCount = true;
component.list.issuesSize = 20;
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 1465ef5855f..c63f15e5880 100644
--- a/spec/javascripts/vue_shared/components/table_pagination_spec.js
+++ b/spec/javascripts/vue_shared/components/table_pagination_spec.js
@@ -32,7 +32,7 @@ describe('Pagination component', () => {
change: spy,
});
- expect(component.$el.innerHTML).not.toBeDefined();
+ expect(component.$el.childNodes.length).toEqual(0);
});
describe('prev button', () => {
@@ -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/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index 68a57826647..8b54d72d6f7 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -14,7 +14,7 @@ describe Gitlab::Regex do
it { is_expected.not_to match('?gitlab') }
end
- describe '.environment_slug_regex' do
+ describe '.environment_name_regex' do
subject { described_class.environment_name_regex }
it { is_expected.to match('foo') }
@@ -24,6 +24,7 @@ describe Gitlab::Regex do
it { is_expected.to match('foo.1') }
it { is_expected.not_to match('9&foo') }
it { is_expected.not_to match('foo-^') }
+ it { is_expected.not_to match('!!()()') }
end
describe '.environment_slug_regex' do
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/helpers_spec.rb b/spec/requests/api/helpers_spec.rb
index 0462f494e15..837389451e8 100644
--- a/spec/requests/api/helpers_spec.rb
+++ b/spec/requests/api/helpers_spec.rb
@@ -68,6 +68,12 @@ describe API::Helpers do
end
it { is_expected.to eq(user) }
+
+ it 'sets the environment with data of the current user' do
+ subject
+
+ expect(env[API::Helpers::API_USER_ENV]).to eq({ user_id: subject.id, username: subject.username })
+ end
end
context "HEAD request" do
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/tasks/gitlab/uploads_rake_spec.rb b/spec/tasks/gitlab/uploads_rake_spec.rb
new file mode 100644
index 00000000000..ac0005e51e0
--- /dev/null
+++ b/spec/tasks/gitlab/uploads_rake_spec.rb
@@ -0,0 +1,27 @@
+require 'rake_helper'
+
+describe 'gitlab:uploads rake tasks' do
+ describe 'check' do
+ let!(:upload) { create(:upload, path: Rails.root.join('spec/fixtures/banana_sample.gif')) }
+
+ before do
+ Rake.application.rake_require 'tasks/gitlab/uploads'
+ end
+
+ it 'outputs the integrity check for each uploaded file' do
+ expect { run_rake_task('gitlab:uploads:check') }.to output(/Checking file \(#{upload.id}\): #{Regexp.quote(upload.absolute_path)}/).to_stdout
+ end
+
+ it 'errors out about missing files on the file system' do
+ create(:upload)
+
+ expect { run_rake_task('gitlab:uploads:check') }.to output(/File does not exist on the file system/).to_stdout
+ end
+
+ it 'errors out about invalid checksum' do
+ upload.update_column(:checksum, '01a3156db2cf4f67ec823680b40b7302f89ab39179124ad219f94919b8a1769e')
+
+ expect { run_rake_task('gitlab:uploads:check') }.to output(/File checksum \(9e697aa09fe196909813ee36103e34f721fe47a5fdc8aac0e4e4ac47b9b38282\) does not match the one in the database \(#{upload.checksum}\)/).to_stdout
+ end
+ end
+end
diff --git a/spec/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"